Bug 1312808 - rewrite stored WebAssembly.Module when build-id changes; r=asuth,luke
authorJan Varga <jan.varga@gmail.com>
Thu, 08 Dec 2016 15:37:13 -1000
changeset 325434 6106512db4baa60f3ef5909d22a661b2228e8052
parent 325433 8ec6d1a356d132809281b8bd227abbfcc3318904
child 325435 8cc548682ae3f4fede10b12f12b8d7874297c9b4
push id31054
push usercbook@mozilla.com
push dateFri, 09 Dec 2016 12:32:25 +0000
treeherdermozilla-central@44ab7f53ead5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, luke
bugs1312808
milestone53.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 1312808 - rewrite stored WebAssembly.Module when build-id changes; r=asuth,luke
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IndexedDatabase.h
dom/indexedDB/IndexedDatabaseInlines.h
dom/indexedDB/test/unit/test_file_copy_failure.js
dom/indexedDB/test/unit/test_wasm_recompile.js
dom/indexedDB/test/unit/wasm_recompile_profile.zip
dom/indexedDB/test/unit/xpcshell-head-parent-process.js
dom/indexedDB/test/unit/xpcshell-parent-process.ini
js/src/jsapi.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmModule.h
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -684,28 +684,33 @@ DeserializeStructuredCloneFiles(
           StructuredCloneFile* file = aFiles.AppendElement();
           MOZ_ASSERT(file);
 
           file->mType = StructuredCloneFile::eStructuredClone;
 
           break;
         }
 
-        case StructuredCloneFile::eWasmBytecode: {
+        case StructuredCloneFile::eWasmBytecode:
+        case StructuredCloneFile::eWasmCompiled: {
           if (aModuleSet) {
             MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
 
             StructuredCloneFile* file = aFiles.AppendElement();
             MOZ_ASSERT(file);
 
             file->mType = serializedFile.type();
 
             MOZ_ASSERT(moduleIndex < aModuleSet->Length());
             file->mWasmModule = aModuleSet->ElementAt(moduleIndex);
 
+            if (serializedFile.type() == StructuredCloneFile::eWasmCompiled) {
+              moduleIndex++;
+            }
+
             break;
           }
 
           MOZ_ASSERT(blobOrMutableFile.type() ==
                        BlobOrMutableFile::TPBlobChild);
 
           auto* actor =
             static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
@@ -715,78 +720,22 @@ DeserializeStructuredCloneFiles(
 
           RefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
 
           aDatabase->NoteReceivedBlob(blob);
 
           StructuredCloneFile* file = aFiles.AppendElement();
           MOZ_ASSERT(file);
 
-          file->mType = StructuredCloneFile::eWasmBytecode;
+          file->mType = serializedFile.type();
           file->mBlob.swap(blob);
 
           break;
         }
 
-        case StructuredCloneFile::eWasmCompiled: {
-          if (aModuleSet) {
-            MOZ_ASSERT(blobOrMutableFile.type() == BlobOrMutableFile::Tnull_t);
-
-            StructuredCloneFile* file = aFiles.AppendElement();
-            MOZ_ASSERT(file);
-
-            file->mType = serializedFile.type();
-
-            MOZ_ASSERT(moduleIndex < aModuleSet->Length());
-            file->mWasmModule = aModuleSet->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);
-
-              file->mType = StructuredCloneFile::eWasmCompiled;
-
-              break;
-            }
-
-            case BlobOrMutableFile::TPBlobChild: {
-              auto* actor =
-                static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
-
-              RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
-              MOZ_ASSERT(blobImpl);
-
-              RefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
-
-              aDatabase->NoteReceivedBlob(blob);
-
-              StructuredCloneFile* file = aFiles.AppendElement();
-              MOZ_ASSERT(file);
-
-              file->mType = StructuredCloneFile::eWasmCompiled;
-              file->mBlob.swap(blob);
-
-              break;
-            }
-
-            default:
-              MOZ_CRASH("Should never get here!");
-          }
-
-          break;
-        }
-
         default:
           MOZ_CRASH("Should never get here!");
       }
     }
   }
 }
 
 void
@@ -3010,33 +2959,32 @@ PreprocessHelper::Init(const nsTArray<St
     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);
+    MOZ_ASSERT(compiledFile.mBlob);
 
     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();
-      }
+    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;
@@ -3099,24 +3047,21 @@ PreprocessHelper::RunOnStreamTransportTh
 
     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;
+    MOZ_ASSERT(compiledStream);
+
+    PRFileDesc* compiledFileDesc = GetFileDescriptorFromStream(compiledStream);
+    if (NS_WARN_IF(!compiledFileDesc)) {
+      return NS_ERROR_FAILURE;
     }
 
     JS::BuildIdCharVector buildId;
     bool ok = GetBuildId(&buildId);
     if (NS_WARN_IF(!ok)) {
       return NS_ERROR_FAILURE;
     }
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -87,16 +87,17 @@
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsQueryObject.h"
 #include "nsRefPtrHashtable.h"
 #include "nsStreamUtils.h"
 #include "nsString.h"
+#include "nsStringStream.h"
 #include "nsThreadPool.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "PermissionRequestBase.h"
 #include "ProfilerHelpers.h"
 #include "prsystem.h"
 #include "prtime.h"
 #include "ReportInternalError.h"
@@ -8191,22 +8192,16 @@ private:
   ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp() override = default;
 
   nsresult
   RemoveOldIndexDataValues(DatabaseConnection* aConnection);
 
-  nsresult
-  CopyFileData(nsIInputStream* aInputStream,
-               nsIOutputStream* aOutputStream,
-               char* aBuffer,
-               uint32_t aBufferSize);
-
   bool
   Init(TransactionBase* aTransaction) override;
 
   nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   void
   GetResponse(RequestResponse& aResponse) override;
@@ -9583,16 +9578,70 @@ private:
   }
 
   NS_DECL_NSITHREADOBSERVER
 };
 
 #endif // DEBUG
 
 /*******************************************************************************
+ * Helper classes
+ ******************************************************************************/
+
+class MOZ_STACK_CLASS FileHelper final
+{
+  RefPtr<FileManager> mFileManager;
+
+  nsCOMPtr<nsIFile> mFileDirectory;
+  nsCOMPtr<nsIFile> mJournalDirectory;
+
+public:
+  explicit FileHelper(FileManager* aFileManager)
+    : mFileManager(aFileManager)
+  { }
+
+  nsresult
+  Init();
+
+  already_AddRefed<nsIFile>
+  GetFile(FileInfo* aFileInfo);
+
+  already_AddRefed<nsIFile>
+  GetCheckedFile(FileInfo* aFileInfo);
+
+  already_AddRefed<nsIFile>
+  GetJournalFile(FileInfo* aFileInfo);
+
+  nsresult
+  CreateFileFromStream(nsIFile* aFile,
+                       nsIFile* aJournalFile,
+                       nsIInputStream* aInputStream,
+                       bool aCompress);
+
+  nsresult
+  ReplaceFile(nsIFile* aFile,
+              nsIFile* aNewFile,
+              nsIFile* aNewJournalFile);
+
+  nsresult
+  RemoveFile(nsIFile* aFile,
+             nsIFile* aJournalFile);
+
+  already_AddRefed<FileInfo>
+  GetNewFileInfo();
+
+private:
+  nsresult
+  SyncCopy(nsIInputStream* aInputStream,
+           nsIOutputStream* aOutputStream,
+           char* aBuffer,
+           uint32_t aBufferSize);
+};
+
+/*******************************************************************************
  * Helper Functions
  ******************************************************************************/
 
 bool
 TokenizerIgnoreNothing(char16_t /* aChar */)
 {
   return false;
 }
@@ -9647,78 +9696,200 @@ DeserializeStructuredCloneFile(FileManag
 
   aFile->mFileInfo.swap(fileInfo);
   aFile->mType = type;
 
   return NS_OK;
 }
 
 nsresult
+CheckWasmModule(FileHelper* aFileHelper,
+                StructuredCloneFile* aBytecodeFile,
+                StructuredCloneFile* aCompiledFile)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileHelper);
+  MOZ_ASSERT(aBytecodeFile);
+  MOZ_ASSERT(aCompiledFile);
+  MOZ_ASSERT(aBytecodeFile->mType == StructuredCloneFile::eWasmBytecode);
+  MOZ_ASSERT(aCompiledFile->mType == StructuredCloneFile::eWasmCompiled);
+
+  nsCOMPtr<nsIFile> compiledFile =
+    aFileHelper->GetCheckedFile(aCompiledFile->mFileInfo);
+  if (NS_WARN_IF(!compiledFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+
+  bool match;
+  {
+    ScopedPRFileDesc compiledFileDesc;
+    rv = compiledFile->OpenNSPRFileDesc(PR_RDONLY,
+                                        0644,
+                                        &compiledFileDesc.rwget());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    JS::BuildIdCharVector buildId;
+    bool ok = GetBuildId(&buildId);
+    if (NS_WARN_IF(!ok)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    match = JS::CompiledWasmModuleAssumptionsMatch(compiledFileDesc,
+                                                   Move(buildId));
+  }
+  if (match) {
+    return NS_OK;
+  }
+
+  // Re-compile the module.  It would be preferable to do this in the child
+  // (content process) instead of here in the parent, but that would be way more
+  // complex and without significant memory allocation or security benefits.
+  // See the discussion starting from
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1312808#c9 for more details.
+  nsCOMPtr<nsIFile> bytecodeFile =
+    aFileHelper->GetCheckedFile(aBytecodeFile->mFileInfo);
+  if (NS_WARN_IF(!bytecodeFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ScopedPRFileDesc bytecodeFileDesc;
+  rv = bytecodeFile->OpenNSPRFileDesc(PR_RDONLY,
+                                      0644,
+                                      &bytecodeFileDesc.rwget());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  JS::BuildIdCharVector buildId;
+  bool ok = GetBuildId(&buildId);
+  if (NS_WARN_IF(!ok)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
+                                                            nullptr,
+                                                            Move(buildId),
+                                                            nullptr,
+                                                            0,
+                                                            0);
+  if (NS_WARN_IF(!module)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  size_t compiledSize;
+  module->serializedSize(nullptr, &compiledSize);
+
+  UniquePtr<uint8_t[]> compiled(new (fallible) uint8_t[compiledSize]);
+  if (NS_WARN_IF(!compiled)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  module->serialize(nullptr, 0, compiled.get(), compiledSize);
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = NS_NewByteInputStream(getter_AddRefs(inputStream),
+                             reinterpret_cast<const char*>(compiled.get()),
+                             compiledSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  RefPtr<FileInfo> newFileInfo = aFileHelper->GetNewFileInfo();
+
+  nsCOMPtr<nsIFile> newFile = aFileHelper->GetFile(newFileInfo);
+  if (NS_WARN_IF(!newFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIFile> newJournalFile =
+    aFileHelper->GetJournalFile(newFileInfo);
+  if (NS_WARN_IF(!newJournalFile)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  rv = aFileHelper->CreateFileFromStream(newFile,
+                                         newJournalFile,
+                                         inputStream,
+                                         /* aCompress */ false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv2))) {
+      return rv;
+    }
+    return rv;
+  }
+
+  rv = aFileHelper->ReplaceFile(compiledFile, newFile, newJournalFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv2))) {
+      return rv;
+    }
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 DeserializeStructuredCloneFiles(FileManager* aFileManager,
                                 const nsAString& aText,
                                 nsTArray<StructuredCloneFile>& aResult,
                                 bool* aHasPreprocessInfo)
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
-  MOZ_ASSERT(aHasPreprocessInfo);
 
   nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
     tokenizer(aText, ' ');
 
   nsAutoString token;
   nsresult rv;
-  nsCOMPtr<nsIFile> directory;
+  Maybe<FileHelper> fileHelper;
 
   while (tokenizer.hasMoreTokens()) {
     token = tokenizer.nextToken();
     MOZ_ASSERT(!token.IsEmpty());
 
     StructuredCloneFile* file = aResult.AppendElement();
     rv = DeserializeStructuredCloneFile(aFileManager, token, file);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    if (!aHasPreprocessInfo) {
+      continue;
+    }
+
     if (file->mType == StructuredCloneFile::eWasmBytecode) {
-      MOZ_ASSERT(file->mValid);
-
       *aHasPreprocessInfo = 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();
-      MOZ_ASSERT(fileId > 0);
-
-      nsCOMPtr<nsIFile> nativeFile =
-        aFileManager->GetCheckedFileForId(directory, fileId);
-      if (NS_WARN_IF(!nativeFile)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      ScopedPRFileDesc fileDesc;
-      rv = nativeFile->OpenNSPRFileDesc(PR_RDONLY, 0644, &fileDesc.rwget());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      JS::BuildIdCharVector buildId;
-      bool ok = GetBuildId(&buildId);
-      if (NS_WARN_IF(!ok)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      MOZ_ASSERT(file->mValid);
-      file->mValid = JS::CompiledWasmModuleAssumptionsMatch(fileDesc,
-                                                            Move(buildId));
+      if (fileHelper.isNothing()) {
+        fileHelper.emplace(aFileManager);
+
+        rv = fileHelper->Init();
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+      }
+
+      MOZ_ASSERT(aResult.Length() > 1);
+      MOZ_ASSERT(aResult[aResult.Length() - 2].mType ==
+                   StructuredCloneFile::eWasmBytecode);
+
+      StructuredCloneFile* previousFile = &aResult[aResult.Length() - 2];
+
+      rv = CheckWasmModule(fileHelper.ptr(), previousFile, file);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
 
       *aHasPreprocessInfo = true;
     }
   }
 
   return NS_OK;
 }
 
@@ -9860,17 +10031,17 @@ SerializeStructuredCloneFiles(
         file->file() = null_t();
         file->type() = StructuredCloneFile::eStructuredClone;
 
         break;
       }
 
       case StructuredCloneFile::eWasmBytecode:
       case StructuredCloneFile::eWasmCompiled: {
-        if (!aForPreprocess || !file.mValid) {
+        if (!aForPreprocess) {
           SerializedStructuredCloneFile* serializedFile =
             aResult.AppendElement(fallible);
           MOZ_ASSERT(serializedFile);
 
           serializedFile->file() = null_t();
           serializedFile->type() = file.mType;
         } else {
           RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
@@ -11475,18 +11646,17 @@ UpdateRefcountFunction::ProcessValue(moz
 
   nsString ids;
   rv = aValues->GetString(aIndex, ids);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsTArray<StructuredCloneFile> files;
-  bool dummy;
-  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, &dummy);
+  rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, nullptr);
   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();
@@ -25795,73 +25965,16 @@ ObjectStoreAddOrPutRequestOp::RemoveOldI
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-nsresult
-ObjectStoreAddOrPutRequestOp::CopyFileData(nsIInputStream* aInputStream,
-                                           nsIOutputStream* aOutputStream,
-                                           char* aBuffer,
-                                           uint32_t aBufferSize)
-{
-  AssertIsOnConnectionThread();
-  MOZ_ASSERT(aInputStream);
-  MOZ_ASSERT(aOutputStream);
-
-  PROFILER_LABEL("IndexedDB",
-                 "ObjectStoreAddOrPutRequestOp::CopyFileData",
-                 js::ProfileEntry::Category::STORAGE);
-
-  nsresult rv;
-
-  do {
-    uint32_t numRead;
-    rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      break;
-    }
-
-    if (!numRead) {
-      break;
-    }
-
-    uint32_t numWrite;
-    rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
-    if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
-      rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      break;
-    }
-
-    if (NS_WARN_IF(numWrite != numRead)) {
-      rv = NS_ERROR_FAILURE;
-      break;
-    }
-  } while (true);
-
-  if (NS_SUCCEEDED(rv)) {
-    rv = aOutputStream->Flush();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  nsresult rv2 = aOutputStream->Close();
-  if (NS_WARN_IF(NS_FAILED(rv2))) {
-    return NS_SUCCEEDED(rv) ? rv2 : rv;
-  }
-
-  return rv;
-}
-
 bool
 ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction)
 {
   AssertIsOnOwningThread();
 
   const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
     mParams.indexUpdateInfos();
 
@@ -26192,201 +26305,78 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
 
     rv = stmt->BindAdoptedBlobByName(NS_LITERAL_CSTRING("data"), dataBuffer,
                                      dataBufferLength);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
-  nsCOMPtr<nsIFile> fileDirectory;
-  nsCOMPtr<nsIFile> journalDirectory;
+  Maybe<FileHelper> fileHelper;
 
   if (mFileManager) {
-    fileDirectory = mFileManager->GetDirectory();
-    if (NS_WARN_IF(!fileDirectory)) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    journalDirectory = mFileManager->EnsureJournalDirectory();
-    if (NS_WARN_IF(!journalDirectory)) {
+    fileHelper.emplace(mFileManager);
+
+    rv = fileHelper->Init();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
-
-    DebugOnly<bool> exists;
-    MOZ_ASSERT(NS_SUCCEEDED(fileDirectory->Exists(&exists)));
-    MOZ_ASSERT(exists);
-
-    DebugOnly<bool> isDirectory;
-    MOZ_ASSERT(NS_SUCCEEDED(fileDirectory->IsDirectory(&isDirectory)));
-    MOZ_ASSERT(isDirectory);
-
-    MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
-    MOZ_ASSERT(exists);
-
-    MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
-    MOZ_ASSERT(isDirectory);
   }
 
   if (!mStoredFileInfos.IsEmpty()) {
     nsAutoString fileIds;
 
     for (uint32_t count = mStoredFileInfos.Length(), index = 0;
          index < count;
          index++) {
       StoredFileInfo& storedFileInfo = mStoredFileInfos[index];
       MOZ_ASSERT(storedFileInfo.mFileInfo);
 
-      const int64_t id = storedFileInfo.mFileInfo->Id();
-
       nsCOMPtr<nsIInputStream> inputStream;
       storedFileInfo.mInputStream.swap(inputStream);
 
       if (inputStream) {
-        MOZ_ASSERT(fileDirectory);
-        MOZ_ASSERT(journalDirectory);
-
-        nsCOMPtr<nsIFile> diskFile =
-          mFileManager->GetFileForId(fileDirectory, id);
-        if (NS_WARN_IF(!diskFile)) {
+        RefPtr<FileInfo>& fileInfo = storedFileInfo.mFileInfo;
+
+        nsCOMPtr<nsIFile> file = fileHelper->GetFile(fileInfo);
+        if (NS_WARN_IF(!file)) {
           IDB_REPORT_INTERNAL_ERR();
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
-        bool exists;
-        rv = diskFile->Exists(&exists);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
+        nsCOMPtr<nsIFile> journalFile =
+          fileHelper->GetJournalFile(fileInfo);
+        if (NS_WARN_IF(!journalFile)) {
           IDB_REPORT_INTERNAL_ERR();
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
-        if (exists) {
-          bool isFile;
-          rv = diskFile->IsFile(&isFile);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(!isFile)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          uint64_t inputStreamSize;
-          rv = inputStream->Available(&inputStreamSize);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          int64_t fileSize;
-          rv = diskFile->GetFileSize(&fileSize);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(fileSize < 0)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (NS_WARN_IF(uint64_t(fileSize) != inputStreamSize)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-        } else {
-          // Create a journal file first.
-          nsCOMPtr<nsIFile> journalFile =
-            mFileManager->GetFileForId(journalDirectory, id);
-          if (NS_WARN_IF(!journalFile)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          rv = journalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          // Now try to copy the stream.
-          RefPtr<FileOutputStream> fileOutputStream =
-            FileOutputStream::Create(mPersistenceType,
-                                     mGroup,
-                                     mOrigin,
-                                     diskFile);
-          if (NS_WARN_IF(!fileOutputStream)) {
-            IDB_REPORT_INTERNAL_ERR();
-            return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-
-          if (storedFileInfo.mType == StructuredCloneFile::eStructuredClone) {
-            RefPtr<SnappyCompressOutputStream> snappyOutputStream =
-              new SnappyCompressOutputStream(fileOutputStream);
-
-            UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
-
-            rv = CopyFileData(inputStream,
-                              snappyOutputStream,
-                              buffer.get(),
-                              snappyOutputStream->BlockSize());
-          } else {
-            char buffer[kFileCopyBufferSize];
-
-            rv = CopyFileData(inputStream,
-                              fileOutputStream,
-                              buffer,
-                              kFileCopyBufferSize);
-          }
-          if (NS_FAILED(rv) &&
-              NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
-            IDB_REPORT_INTERNAL_ERR();
-            rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-          }
-          if (NS_WARN_IF(NS_FAILED(rv))) {
-            // Try to remove the file if the copy failed.
-            nsresult rv2;
-            int64_t fileSize;
-
-            if (mFileManager->EnforcingQuota()) {
-              rv2 = diskFile->GetFileSize(&fileSize);
-              if (NS_WARN_IF(NS_FAILED(rv2))) {
-                return rv;
-              }
-            }
-
-            rv2 = diskFile->Remove(false);
-            if (NS_WARN_IF(NS_FAILED(rv2))) {
-              return rv;
-            }
-
-            rv2 = journalFile->Remove(false);
-            if (NS_WARN_IF(NS_FAILED(rv2))) {
-              return rv;
-            }
-
-            if (mFileManager->EnforcingQuota()) {
-              QuotaManager* quotaManager = QuotaManager::Get();
-              MOZ_ASSERT(quotaManager);
-
-              quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
-                                                   mFileManager->Group(),
-                                                   mFileManager->Origin(),
-                                                   fileSize);
-            }
-
+        bool compress =
+          storedFileInfo.mType == StructuredCloneFile::eStructuredClone;
+
+        rv = fileHelper->CreateFileFromStream(file,
+                                              journalFile,
+                                              inputStream,
+                                              compress);
+        if (NS_FAILED(rv) &&
+            NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
+          IDB_REPORT_INTERNAL_ERR();
+          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          // Try to remove the file if the copy failed.
+          nsresult rv2 = fileHelper->RemoveFile(file, journalFile);
+          if (NS_WARN_IF(NS_FAILED(rv2))) {
             return rv;
           }
-
-          storedFileInfo.mCopiedSuccessfully = true;
-        }
+          return rv;
+        }
+
+        storedFileInfo.mCopiedSuccessfully = true;
       }
 
       if (index) {
         fileIds.Append(' ');
       }
       storedFileInfo.Serialize(fileIds);
     }
 
@@ -29139,16 +29129,360 @@ DEBUGThreadSlower::AfterProcessNextEvent
 
   MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
                     PR_SUCCESS);
   return NS_OK;
 }
 
 #endif // DEBUG
 
+nsresult
+FileHelper::Init()
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(mFileManager);
+
+  nsCOMPtr<nsIFile> fileDirectory = mFileManager->GetCheckedDirectory();
+  if (NS_WARN_IF(!fileDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIFile> journalDirectory = mFileManager->EnsureJournalDirectory();
+  if (NS_WARN_IF(!journalDirectory)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  DebugOnly<bool> exists;
+  MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
+  MOZ_ASSERT(exists);
+
+  DebugOnly<bool> isDirectory;
+  MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
+  MOZ_ASSERT(isDirectory);
+
+  mFileDirectory = Move(fileDirectory);
+  mJournalDirectory= Move(journalDirectory);
+
+  return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetFileForId(mFileDirectory, fileId);
+  return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetCheckedFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetCheckedFileForId(mFileDirectory, fileId);
+  return file.forget();
+}
+
+already_AddRefed<nsIFile>
+FileHelper::GetJournalFile(FileInfo* aFileInfo)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFileInfo);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mJournalDirectory);
+
+  const int64_t fileId = aFileInfo->Id();
+  MOZ_ASSERT(fileId > 0);
+
+  nsCOMPtr<nsIFile> file =
+    mFileManager->GetFileForId(mJournalDirectory, fileId);
+  return file.forget();
+}
+
+nsresult
+FileHelper::CreateFileFromStream(nsIFile* aFile,
+                                 nsIFile* aJournalFile,
+                                 nsIInputStream* aInputStream,
+                                 bool aCompress)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aJournalFile);
+  MOZ_ASSERT(aInputStream);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  bool exists;
+  nsresult rv = aFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // DOM blobs that are being stored in IDB are cached by calling
+  // IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
+  // again under a different key or in a different object store, we just add
+  // a new reference instead of creating a new copy (all such stored blobs share
+  // the same id).
+  // However, it can happen that CreateFileFromStream failed due to quota
+  // exceeded error and for some reason the orphaned file couldn't be deleted
+  // immediately. Now, if the operation is being repeated, the DOM blob is
+  // already cached, so it has the same file id which clashes with the orphaned
+  // file. We could do some tricks to restore previous copy loop, but it's safer
+  // to just delete the orphaned file and start from scratch.
+  // This corner case is partially simulated in test_file_copy_failure.js
+  if (exists) {
+    bool isFile;
+    rv = aFile->IsFile(&isFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!isFile)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    rv = aJournalFile->Exists(&exists);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!exists)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    rv = aJournalFile->IsFile(&isFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!isFile)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    IDB_WARNING("Deleting orphaned file!");
+
+    rv = RemoveFile(aFile, aJournalFile);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Create a journal file first.
+  rv = aJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Now try to copy the stream.
+  RefPtr<FileOutputStream> fileOutputStream =
+    FileOutputStream::Create(mFileManager->Type(),
+                             mFileManager->Group(),
+                             mFileManager->Origin(),
+                             aFile);
+  if (NS_WARN_IF(!fileOutputStream)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aCompress) {
+    RefPtr<SnappyCompressOutputStream> snappyOutputStream =
+      new SnappyCompressOutputStream(fileOutputStream);
+
+    UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
+
+    rv = SyncCopy(aInputStream,
+                  snappyOutputStream,
+                  buffer.get(),
+                  snappyOutputStream->BlockSize());
+  } else {
+    char buffer[kFileCopyBufferSize];
+
+    rv = SyncCopy(aInputStream,
+                  fileOutputStream,
+                  buffer,
+                  kFileCopyBufferSize);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FileHelper::ReplaceFile(nsIFile* aFile,
+                        nsIFile* aNewFile,
+                        nsIFile* aNewJournalFile)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aNewFile);
+  MOZ_ASSERT(aNewJournalFile);
+  MOZ_ASSERT(mFileManager);
+  MOZ_ASSERT(mFileDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  nsresult rv;
+
+  int64_t fileSize;
+
+  if (mFileManager->EnforcingQuota()) {
+    rv = aFile->GetFileSize(&fileSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsAutoString fileName;
+  rv = aFile->GetLeafName(fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aNewFile->RenameTo(nullptr, fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mFileManager->EnforcingQuota()) {
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+                                         mFileManager->Group(),
+                                         mFileManager->Origin(),
+                                         fileSize);
+  }
+
+  rv = aNewJournalFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FileHelper::RemoveFile(nsIFile* aFile,
+                       nsIFile* aJournalFile)
+{
+  nsresult rv;
+
+  int64_t fileSize;
+
+  if (mFileManager->EnforcingQuota()) {
+    rv = aFile->GetFileSize(&fileSize);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  rv = aFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mFileManager->EnforcingQuota()) {
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+                                         mFileManager->Group(),
+                                         mFileManager->Origin(),
+                                         fileSize);
+  }
+
+  rv = aJournalFile->Remove(false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<FileInfo>
+FileHelper::GetNewFileInfo()
+{
+  MOZ_ASSERT(mFileManager);
+
+  return mFileManager->GetNewFileInfo();
+}
+
+nsresult
+FileHelper::SyncCopy(nsIInputStream* aInputStream,
+                     nsIOutputStream* aOutputStream,
+                     char* aBuffer,
+                     uint32_t aBufferSize)
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+  MOZ_ASSERT(aInputStream);
+  MOZ_ASSERT(aOutputStream);
+
+  PROFILER_LABEL("IndexedDB",
+                 "FileHelper::SyncCopy",
+                 js::ProfileEntry::Category::STORAGE);
+
+  nsresult rv;
+
+  do {
+    uint32_t numRead;
+    rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      break;
+    }
+
+    if (!numRead) {
+      break;
+    }
+
+    uint32_t numWrite;
+    rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
+    if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+      rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      break;
+    }
+
+    if (NS_WARN_IF(numWrite != numRead)) {
+      rv = NS_ERROR_FAILURE;
+      break;
+    }
+  } while (true);
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = aOutputStream->Flush();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsresult rv2 = aOutputStream->Close();
+  if (NS_WARN_IF(NS_FAILED(rv2))) {
+    return NS_SUCCEEDED(rv) ? rv2 : rv;
+  }
+
+  return rv;
+}
+
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
 
 #undef IDB_MOBILE
 #undef IDB_DEBUG_LOG
 #undef ASSERT_UNLESS_FUZZING
 #undef DISABLE_ASSERTS_FOR_FUZZING
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -38,18 +38,16 @@ struct StructuredCloneFile
     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();
 
   // In IndexedDatabaseInlines.h
   inline
   ~StructuredCloneFile();
--- a/dom/indexedDB/IndexedDatabaseInlines.h
+++ b/dom/indexedDB/IndexedDatabaseInlines.h
@@ -19,17 +19,16 @@
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 inline
 StructuredCloneFile::StructuredCloneFile()
   : mType(eBlob)
-  , mValid(true)
 {
   MOZ_COUNT_CTOR(StructuredCloneFile);
 }
 
 inline
 StructuredCloneFile::~StructuredCloneFile()
 {
   MOZ_COUNT_DTOR(StructuredCloneFile);
@@ -37,18 +36,17 @@ StructuredCloneFile::~StructuredCloneFil
 
 inline
 bool
 StructuredCloneFile::operator==(const StructuredCloneFile& aOther) const
 {
   return this->mBlob == aOther.mBlob &&
          this->mMutableFile == aOther.mMutableFile &&
          this->mFileInfo == aOther.mFileInfo &&
-         this->mType == aOther.mType &&
-         this->mValid == aOther.mValid;
+         this->mType == aOther.mType;
 }
 
 inline
 StructuredCloneReadInfo::StructuredCloneReadInfo()
   : mDatabase(nullptr)
   , mHasPreprocessInfo(false)
 {
   MOZ_COUNT_CTOR(StructuredCloneReadInfo);
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_file_copy_failure.js
@@ -0,0 +1,75 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const name = "test_file_copy_failure.js";
+  const objectStoreName = "Blobs";
+  const blob = getBlob(getView(1024));
+
+  info("Opening database");
+
+  let request = indexedDB.open(name);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = continueToNextStepSync;
+  request.onsuccess = unexpectedSuccessHandler;
+  yield undefined;
+
+  // upgradeneeded
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = continueToNextStepSync;
+
+  info("Creating objectStore");
+
+  request.result.createObjectStore(objectStoreName);
+
+  yield undefined;
+
+  // success
+  let db = request.result;
+  db.onerror = errorHandler;
+
+  info("Creating orphaned file");
+
+  let filesDir = getChromeFilesDir();
+
+  let journalFile = filesDir.clone();
+  journalFile.append("journals");
+  journalFile.append("1");
+
+  let exists = journalFile.exists();
+  ok(!exists, "Journal file doesn't exist");
+
+  journalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+  let file = filesDir.clone();
+  file.append("1");
+
+  exists = file.exists();
+  ok(!exists, "File doesn't exist");
+
+  file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+  info("Storing blob");
+
+  let trans = db.transaction(objectStoreName, "readwrite");
+
+  request = trans.objectStore(objectStoreName).add(blob, 1);
+  request.onsuccess = continueToNextStepSync;
+
+  yield undefined;
+
+  trans.oncomplete = continueToNextStepSync;
+
+  yield undefined;
+
+  exists = journalFile.exists();
+  ok(!exists, "Journal file doesn't exist");
+
+  finishTest();
+  yield undefined;
+}
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_wasm_recompile.js
@@ -0,0 +1,124 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const name = "test_wasm_recompile.js";
+
+  const objectStoreName = "Wasm";
+
+  const wasmData = { key: 1, wasm: null };
+
+  // The goal of this test is to prove that wasm is recompiled and the on-disk
+  // copy updated.
+
+  if (!isWasmSupported()) {
+    finishTest();
+    yield undefined;
+  }
+
+  getWasmBinary('(module (func (nop)))');
+  let binary = yield undefined;
+
+  wasmData.wasm = getWasmModule(binary);
+
+  info("Installing profile");
+
+  clearAllDatabases(continueToNextStepSync);
+  yield undefined;
+
+  // The profile was created by an older build (buildId: 20161116145318,
+  // cpuId: X64=0x2). It contains one stored wasm module (file id 1 - bytecode
+  // and file id 2 - compiled/machine code). The file create_db.js in the
+  // package was run locally (specifically it was temporarily added to
+  // xpcshell-parent-process.ini and then executed:
+  // mach xpcshell-test dom/indexedDB/test/unit/create_db.js
+  installPackagedProfile("wasm_recompile_profile");
+
+  let filesDir = getChromeFilesDir();
+
+  let file = filesDir.clone();
+  file.append("2");
+
+  info("Reading out contents of compiled blob");
+
+  let fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let compiledBuffer = fileReader.result;
+
+  info("Opening database");
+
+  let request = indexedDB.open(name);
+  request.onerror = errorHandler;
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  // success
+  let db = request.result;
+  db.onerror = errorHandler;
+
+  info("Getting wasm");
+
+  request = db.transaction([objectStoreName])
+              .objectStore(objectStoreName).get(wasmData.key);
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  info("Verifying wasm module");
+
+  verifyWasmModule(request.result, wasmData.wasm);
+  yield undefined;
+
+  info("Reading out contents of new compiled blob");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let newCompiledBuffer = fileReader.result;
+
+  info("Verifying blobs differ");
+
+  ok(!compareBuffers(newCompiledBuffer, compiledBuffer), "Blobs differ");
+
+  info("Getting wasm again");
+
+  request = db.transaction([objectStoreName])
+              .objectStore(objectStoreName).get(wasmData.key);
+  request.onsuccess = continueToNextStepSync;
+  yield undefined;
+
+  info("Verifying wasm module");
+
+  verifyWasmModule(request.result, wasmData.wasm);
+  yield undefined;
+
+  info("Reading contents of new compiled blob again");
+
+  fileReader = new FileReader();
+  fileReader.onload = continueToNextStepSync;
+  fileReader.readAsArrayBuffer(File.createFromNsIFile(file));
+
+  yield undefined;
+
+  let newCompiledBuffer2 = fileReader.result;
+
+  info("Verifying blob didn't change");
+
+  ok(compareBuffers(newCompiledBuffer2, newCompiledBuffer),
+     "Blob didn't change");
+
+  finishTest();
+  yield undefined;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5e1144db47be8c85f4545c978a5eb90d3f8293ec
GIT binary patch
literal 5447
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
F{{cE*<cR<P
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -342,16 +342,41 @@ function installPackagedProfile(packageN
       istream.close();
       bostream.close();
     }
   }
 
   zipReader.close();
 }
 
+function getChromeFilesDir()
+{
+  let dirService = Cc["@mozilla.org/file/directory_service;1"]
+                   .getService(Ci.nsIProperties);
+
+  let profileDir = dirService.get("ProfD", Ci.nsIFile);
+
+  let idbDir = profileDir.clone();
+  idbDir.append("storage");
+  idbDir.append("permanent");
+  idbDir.append("chrome");
+  idbDir.append("idb");
+
+  let idbEntries = idbDir.directoryEntries;
+  while (idbEntries.hasMoreElements()) {
+    let entry = idbEntries.getNext();
+    let file = entry.QueryInterface(Ci.nsIFile);
+    if (file.isDirectory()) {
+      return file;
+    }
+  }
+
+  throw new Error("files directory doesn't exist!");
+}
+
 function getView(size)
 {
   let buffer = new ArrayBuffer(size);
   let view = new Uint8Array(buffer);
   is(buffer.byteLength, size, "Correct byte length");
   return view;
 }
 
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini
+++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini
@@ -21,26 +21,28 @@ support-files =
   GlobalObjectsSandbox.js
   metadata2Restore_profile.zip
   metadataRestore_profile.zip
   schema18upgrade_profile.zip
   schema21upgrade_profile.zip
   schema23upgrade_profile.zip
   snappyUpgrade_profile.zip
   storagePersistentUpgrade_profile.zip
+  wasm_recompile_profile.zip
   xpcshell-shared.ini
 
 [include:xpcshell-shared.ini]
 
 [test_blob_file_backed.js]
 [test_bug1056939.js]
 [test_cleanup_transaction.js]
 [test_database_close_without_onclose.js]
 [test_database_onclose.js]
 [test_defaultStorageUpgrade.js]
+[test_file_copy_failure.js]
 [test_idbSubdirUpgrade.js]
 [test_globalObjects_ipc.js]
 skip-if = toolkit == 'android'
 [test_idle_maintenance.js]
 [test_invalidate.js]
 # disabled for the moment.
 skip-if = true
 [test_lowDiskSpace.js]
@@ -57,8 +59,9 @@ skip-if = true
 [test_snappyUpgrade.js]
 [test_storagePersistentUpgrade.js]
 [test_temporary_storage.js]
 # bug 951017: intermittent failure on Android x86 emulator
 skip-if = os == "android" && processor == "x86"
 [test_view_put_get_values.js]
 [test_wasm_getAll.js]
 [test_wasm_put_get_values.js]
+[test_wasm_recompile.js]
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -6087,19 +6087,19 @@ SetBuildIdOp(JSContext* cx, BuildIdOp bu
  * by calling createObject().
  */
 
 struct WasmModule : js::AtomicRefCounted<WasmModule>
 {
     MOZ_DECLARE_REFCOUNTED_TYPENAME(WasmModule)
     virtual ~WasmModule() {}
 
-    virtual void serializedSize(size_t* bytecodeSize, size_t* compiledSize) const = 0;
-    virtual void serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
-                           uint8_t* compiledBegin, size_t compiledSize) const = 0;
+    virtual void serializedSize(size_t* maybeBytecodeSize, size_t* maybeCompiledSize) const = 0;
+    virtual void serialize(uint8_t* maybeBytecodeBegin, size_t maybeBytecodeSize,
+                           uint8_t* maybeCompiledBegin, size_t maybeCompiledSize) const = 0;
 
     virtual JSObject* createObject(JSContext* cx) = 0;
 };
 
 extern JS_PUBLIC_API(bool)
 IsWasmModuleObject(HandleObject obj);
 
 extern JS_PUBLIC_API(RefPtr<WasmModule>)
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -133,54 +133,64 @@ LinkData::deserialize(const uint8_t* cur
 size_t
 LinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return internalLinks.sizeOfExcludingThis(mallocSizeOf) +
            symbolicLinks.sizeOfExcludingThis(mallocSizeOf);
 }
 
 /* virtual */ void
-Module::serializedSize(size_t* bytecodeSize, size_t* compiledSize) const
+Module::serializedSize(size_t* maybeBytecodeSize, size_t* maybeCompiledSize) const
 {
-    *bytecodeSize = SerializedPodVectorSize(bytecode_->bytes);
+    if (maybeBytecodeSize)
+        *maybeBytecodeSize = SerializedPodVectorSize(bytecode_->bytes);
 
-    *compiledSize = assumptions_.serializedSize() +
-                    SerializedPodVectorSize(code_) +
-                    linkData_.serializedSize() +
-                    SerializedVectorSize(imports_) +
-                    SerializedVectorSize(exports_) +
-                    SerializedPodVectorSize(dataSegments_) +
-                    SerializedVectorSize(elemSegments_) +
-                    metadata_->serializedSize();
+    if (maybeCompiledSize) {
+        *maybeCompiledSize = assumptions_.serializedSize() +
+                             SerializedPodVectorSize(code_) +
+                             linkData_.serializedSize() +
+                             SerializedVectorSize(imports_) +
+                             SerializedVectorSize(exports_) +
+                             SerializedPodVectorSize(dataSegments_) +
+                             SerializedVectorSize(elemSegments_) +
+                             metadata_->serializedSize();
+    }
 }
 
 /* virtual */ void
-Module::serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
-                  uint8_t* compiledBegin, size_t compiledSize) const
+Module::serialize(uint8_t* maybeBytecodeBegin, size_t maybeBytecodeSize,
+                  uint8_t* maybeCompiledBegin, size_t maybeCompiledSize) const
 {
-    // Bytecode deserialization is not guarded by Assumptions and thus must not
-    // change incompatibly between builds.
+    MOZ_ASSERT(!!maybeBytecodeBegin == !!maybeBytecodeSize);
+    MOZ_ASSERT(!!maybeCompiledBegin == !!maybeCompiledSize);
 
-    uint8_t* bytecodeEnd = SerializePodVector(bytecodeBegin, bytecode_->bytes);
-    MOZ_RELEASE_ASSERT(bytecodeEnd == bytecodeBegin + bytecodeSize);
+    if (maybeBytecodeBegin) {
+        // Bytecode deserialization is not guarded by Assumptions and thus must not
+        // change incompatibly between builds.
+
+        uint8_t* bytecodeEnd = SerializePodVector(maybeBytecodeBegin, bytecode_->bytes);
+        MOZ_RELEASE_ASSERT(bytecodeEnd == maybeBytecodeBegin + maybeBytecodeSize);
+    }
 
-    // Assumption must be serialized at the beginning of the compiled bytes so
-    // that compiledAssumptionsMatch can detect a build-id mismatch before any
-    // other decoding occurs.
+    if (maybeCompiledBegin) {
+        // Assumption must be serialized at the beginning of the compiled bytes so
+        // that compiledAssumptionsMatch can detect a build-id mismatch before any
+        // other decoding occurs.
 
-    uint8_t* cursor = compiledBegin;
-    cursor = assumptions_.serialize(cursor);
-    cursor = SerializePodVector(cursor, code_);
-    cursor = linkData_.serialize(cursor);
-    cursor = SerializeVector(cursor, imports_);
-    cursor = SerializeVector(cursor, exports_);
-    cursor = SerializePodVector(cursor, dataSegments_);
-    cursor = SerializeVector(cursor, elemSegments_);
-    cursor = metadata_->serialize(cursor);
-    MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
+        uint8_t* cursor = maybeCompiledBegin;
+        cursor = assumptions_.serialize(cursor);
+        cursor = SerializePodVector(cursor, code_);
+        cursor = linkData_.serialize(cursor);
+        cursor = SerializeVector(cursor, imports_);
+        cursor = SerializeVector(cursor, exports_);
+        cursor = SerializePodVector(cursor, dataSegments_);
+        cursor = SerializeVector(cursor, elemSegments_);
+        cursor = metadata_->serialize(cursor);
+        MOZ_RELEASE_ASSERT(cursor == maybeCompiledBegin + maybeCompiledSize);
+    }
 }
 
 /* static */ bool
 Module::assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin, size_t remain)
 {
     Assumptions cached;
     if (!cached.deserialize(compiledBegin, remain))
         return false;
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -145,19 +145,19 @@ class Module : public JS::WasmModule
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      const ValVector& globalImports,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Structured clone support:
 
-    void serializedSize(size_t* bytecodeSize, size_t* compiledSize) const override;
-    void serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
-                   uint8_t* compiledBegin, size_t compiledSize) const override;
+    void serializedSize(size_t* maybeBytecodeSize, size_t* maybeCompiledSize) const override;
+    void serialize(uint8_t* maybeBytecodeBegin, size_t maybeBytecodeSize,
+                   uint8_t* maybeCompiledBegin, size_t maybeCompiledSize) const override;
     static bool assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin,
                                  size_t remain);
     static RefPtr<Module> deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize,
                                       const uint8_t* compiledBegin, size_t compiledSize,
                                       Metadata* maybeMetadata = nullptr);
     JSObject* createObject(JSContext* cx) override;
 
     // about:memory reporting: