Bug 1312808 - rewrite stored WebAssembly.Module when build-id changes; r=asuth,luke. a=jcristau
authorJan Varga <jan.varga@gmail.com>
Thu, 08 Dec 2016 15:37:13 -1000
changeset 366190 79aee3a6c9164bccdb17d4ad80890af939064d02
parent 366189 d55578d9c7de2239aa8fe5b9bfa8050ae76b64ce
child 366191 f4cf9ed24f6bc878ca3c79341696f8daf0072734
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, luke, jcristau
bugs1312808
milestone52.0a2
Bug 1312808 - rewrite stored WebAssembly.Module when build-id changes; r=asuth,luke. a=jcristau
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
@@ -2995,33 +2944,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;
@@ -3084,24 +3032,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
@@ -89,16 +89,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"
@@ -8229,22 +8230,16 @@ private:
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp()
   { }
 
   nsresult
   RemoveOldIndexDataValues(DatabaseConnection* aConnection);
 
-  nsresult
-  CopyFileData(nsIInputStream* aInputStream,
-               nsIOutputStream* aOutputStream,
-               char* aBuffer,
-               uint32_t aBufferSize);
-
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual void
   GetResponse(RequestResponse& aResponse) override;
@@ -9638,16 +9633,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;
 }
@@ -9702,78 +9751,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;
 }
 
@@ -9915,17 +10086,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,
@@ -11530,18 +11701,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();
@@ -25814,73 +25984,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();
 
@@ -26211,201 +26324,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);
     }
 
@@ -29155,16 +29145,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
@@ -343,16 +343,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
@@ -6083,19 +6083,19 @@ SetBuildIdOp(JSContext* cx, BuildIdOp bu
  * by calling createObject().
  */
 
 struct WasmModule : mozilla::external::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
@@ -259,54 +259,64 @@ ElemSegment::deserialize(const uint8_t* 
 size_t
 ElemSegment::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return elemFuncIndices.sizeOfExcludingThis(mallocSizeOf) +
            elemCodeRangeIndices.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 compiledSize)
 {
     Assumptions cached;
     if (!cached.deserialize(compiledBegin, compiledSize))
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -198,19 +198,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 compiledSize);
     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: