dom/asmjscache/AsmJSCache.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "AsmJSCache.h"
     9 #include <stdio.h>
    11 #include "js/RootingAPI.h"
    12 #include "jsfriendapi.h"
    13 #include "mozilla/Assertions.h"
    14 #include "mozilla/CondVar.h"
    15 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
    16 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
    17 #include "mozilla/dom/ContentChild.h"
    18 #include "mozilla/dom/PermissionMessageUtils.h"
    19 #include "mozilla/dom/quota/Client.h"
    20 #include "mozilla/dom/quota/OriginOrPatternString.h"
    21 #include "mozilla/dom/quota/QuotaManager.h"
    22 #include "mozilla/dom/quota/QuotaObject.h"
    23 #include "mozilla/dom/quota/UsageInfo.h"
    24 #include "mozilla/HashFunctions.h"
    25 #include "mozilla/unused.h"
    26 #include "nsIAtom.h"
    27 #include "nsIFile.h"
    28 #include "nsIPermissionManager.h"
    29 #include "nsIPrincipal.h"
    30 #include "nsIRunnable.h"
    31 #include "nsISimpleEnumerator.h"
    32 #include "nsIThread.h"
    33 #include "nsIXULAppInfo.h"
    34 #include "nsJSPrincipals.h"
    35 #include "nsThreadUtils.h"
    36 #include "nsXULAppAPI.h"
    37 #include "prio.h"
    38 #include "private/pprio.h"
    40 #define ASMJSCACHE_METADATA_FILE_NAME "metadata"
    41 #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
    43 using mozilla::dom::quota::AssertIsOnIOThread;
    44 using mozilla::dom::quota::OriginOrPatternString;
    45 using mozilla::dom::quota::PersistenceType;
    46 using mozilla::dom::quota::QuotaManager;
    47 using mozilla::dom::quota::QuotaObject;
    48 using mozilla::dom::quota::UsageInfo;
    49 using mozilla::unused;
    50 using mozilla::HashString;
    52 namespace mozilla {
    54 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
    56 namespace dom {
    57 namespace asmjscache {
    59 namespace {
    61 bool
    62 IsMainProcess()
    63 {
    64   return XRE_GetProcessType() == GeckoProcessType_Default;
    65 }
    67 // Anything smaller should compile fast enough that caching will just add
    68 // overhead.
    69 static const size_t sMinCachedModuleLength = 10000;
    71 // The number of characters to hash into the Metadata::Entry::mFastHash.
    72 static const unsigned sNumFastHashChars = 4096;
    74 nsresult
    75 WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata)
    76 {
    77   int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;
    79   JS::BuildIdCharVector buildId;
    80   bool ok = GetBuildId(&buildId);
    81   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
    83   ScopedPRFileDesc fd;
    84   nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
    85   NS_ENSURE_SUCCESS(rv, rv);
    87   uint32_t length = buildId.length();
    88   int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
    89   NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);
    91   bytesWritten = PR_Write(fd, buildId.begin(), length);
    92   NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);
    94   bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
    95   NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
    97   return NS_OK;
    98 }
   100 nsresult
   101 ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata)
   102 {
   103   int32_t openFlags = PR_RDONLY;
   105   ScopedPRFileDesc fd;
   106   nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
   107   NS_ENSURE_SUCCESS(rv, rv);
   109   // Read the buildid and check that it matches the current buildid
   111   JS::BuildIdCharVector currentBuildId;
   112   bool ok = GetBuildId(&currentBuildId);
   113   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
   115   uint32_t length;
   116   int32_t bytesRead = PR_Read(fd, &length, sizeof(length));
   117   NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED);
   119   NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED);
   121   JS::BuildIdCharVector fileBuildId;
   122   ok = fileBuildId.resize(length);
   123   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
   125   bytesRead = PR_Read(fd, fileBuildId.begin(), length);
   126   NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED);
   128   for (uint32_t i = 0; i < length; i++) {
   129     if (currentBuildId[i] != fileBuildId[i]) {
   130       return NS_ERROR_FAILURE;
   131     }
   132   }
   134   // Read the Metadata struct
   136   bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata));
   137   NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
   139   return NS_OK;
   140 }
   142 nsresult
   143 GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile)
   144 {
   145   nsCOMPtr<nsIFile> cacheFile;
   146   nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
   147   NS_ENSURE_SUCCESS(rv, rv);
   149   nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
   150   cacheFileName.AppendInt(aModuleIndex);
   151   rv = cacheFile->Append(cacheFileName);
   152   NS_ENSURE_SUCCESS(rv, rv);
   154   cacheFile.forget(aCacheFile);
   155   return NS_OK;
   156 }
   158 class AutoDecreaseUsageForOrigin
   159 {
   160   const nsACString& mGroup;
   161   const nsACString& mOrigin;
   163 public:
   164   uint64_t mFreed;
   166   AutoDecreaseUsageForOrigin(const nsACString& aGroup,
   167                              const nsACString& aOrigin)
   169   : mGroup(aGroup),
   170     mOrigin(aOrigin),
   171     mFreed(0)
   172   { }
   174   ~AutoDecreaseUsageForOrigin()
   175   {
   176     AssertIsOnIOThread();
   178     if (!mFreed) {
   179       return;
   180     }
   182     QuotaManager* qm = QuotaManager::Get();
   183     MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   185     qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY,
   186                                mGroup, mOrigin, mFreed);
   187   }
   188 };
   190 static void
   191 EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
   192              const nsACString& aOrigin, uint64_t aNumBytes,
   193              Metadata& aMetadata)
   194 {
   195   AssertIsOnIOThread();
   197   AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);
   199   for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
   200     Metadata::Entry& entry = aMetadata.mEntries[i];
   201     unsigned moduleIndex = entry.mModuleIndex;
   203     nsCOMPtr<nsIFile> file;
   204     nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
   205     if (NS_WARN_IF(NS_FAILED(rv))) {
   206       return;
   207     }
   209     bool exists;
   210     rv = file->Exists(&exists);
   211     if (NS_WARN_IF(NS_FAILED(rv))) {
   212       return;
   213     }
   215     if (exists) {
   216       int64_t fileSize;
   217       rv = file->GetFileSize(&fileSize);
   218       if (NS_WARN_IF(NS_FAILED(rv))) {
   219         return;
   220       }
   222       rv = file->Remove(false);
   223       if (NS_WARN_IF(NS_FAILED(rv))) {
   224         return;
   225       }
   227       usage.mFreed += fileSize;
   228     }
   230     entry.clear();
   231   }
   232 }
   234 // FileDescriptorHolder owns a file descriptor and its memory mapping.
   235 // FileDescriptorHolder is derived by all three runnable classes (that is,
   236 // (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds,
   237 // FileDescriptorHolder is derived virtually by File and MainProcessRunnable for
   238 // the benefit of SingleProcessRunnable, which derives both. Since File and
   239 // MainProcessRunnable both need to be runnables, FileDescriptorHolder also
   240 // derives nsRunnable.
   241 class FileDescriptorHolder : public nsRunnable
   242 {
   243 public:
   244   FileDescriptorHolder()
   245   : mQuotaObject(nullptr),
   246     mFileSize(INT64_MIN),
   247     mFileDesc(nullptr),
   248     mFileMap(nullptr),
   249     mMappedMemory(nullptr)
   250   { }
   252   ~FileDescriptorHolder()
   253   {
   254     // These resources should have already been released by Finish().
   255     MOZ_ASSERT(!mQuotaObject);
   256     MOZ_ASSERT(!mMappedMemory);
   257     MOZ_ASSERT(!mFileMap);
   258     MOZ_ASSERT(!mFileDesc);
   259   }
   261   size_t
   262   FileSize() const
   263   {
   264     MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
   265     return mFileSize;
   266   }
   268   PRFileDesc*
   269   FileDesc() const
   270   {
   271     MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
   272     return mFileDesc;
   273   }
   275   bool
   276   MapMemory(OpenMode aOpenMode)
   277   {
   278     MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");
   280     PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY
   281                                                           : PR_PROT_READWRITE;
   283     mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
   284     NS_ENSURE_TRUE(mFileMap, false);
   286     mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
   287     NS_ENSURE_TRUE(mMappedMemory, false);
   289     return true;
   290   }
   292   void*
   293   MappedMemory() const
   294   {
   295     MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
   296     return mMappedMemory;
   297   }
   299 protected:
   300   // This method must be called before AllowNextSynchronizedOp (which releases
   301   // the lock protecting these resources). It is idempotent, so it is ok to call
   302   // multiple times (or before the file has been fully opened).
   303   void
   304   Finish()
   305   {
   306     if (mMappedMemory) {
   307       PR_MemUnmap(mMappedMemory, mFileSize);
   308       mMappedMemory = nullptr;
   309     }
   310     if (mFileMap) {
   311       PR_CloseFileMap(mFileMap);
   312       mFileMap = nullptr;
   313     }
   314     if (mFileDesc) {
   315       PR_Close(mFileDesc);
   316       mFileDesc = nullptr;
   317     }
   319     // Holding the QuotaObject alive until all the cache files are closed enables
   320     // assertions in QuotaManager that the cache entry isn't cleared while we
   321     // are working on it.
   322     mQuotaObject = nullptr;
   323   }
   325   nsRefPtr<QuotaObject> mQuotaObject;
   326   int64_t mFileSize;
   327   PRFileDesc* mFileDesc;
   328   PRFileMap* mFileMap;
   329   void* mMappedMemory;
   330 };
   332 // File is a base class shared by (Single|Client)ProcessEntryRunnable that
   333 // presents a single interface to the AsmJSCache ops which need to wait until
   334 // the file is open, regardless of whether we are executing in the main process
   335 // or not.
   336 class File : public virtual FileDescriptorHolder
   337 {
   338 public:
   339   class AutoClose
   340   {
   341     File* mFile;
   343   public:
   344     explicit AutoClose(File* aFile = nullptr)
   345     : mFile(aFile)
   346     { }
   348     void
   349     Init(File* aFile)
   350     {
   351       MOZ_ASSERT(!mFile);
   352       mFile = aFile;
   353     }
   355     File*
   356     operator->() const
   357     {
   358       MOZ_ASSERT(mFile);
   359       return mFile;
   360     }
   362     void
   363     Forget(File** aFile)
   364     {
   365       *aFile = mFile;
   366       mFile = nullptr;
   367     }
   369     ~AutoClose()
   370     {
   371       if (mFile) {
   372         mFile->Close();
   373       }
   374     }
   375   };
   377   bool
   378   BlockUntilOpen(AutoClose* aCloser)
   379   {
   380     MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
   381     MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");
   383     mWaiting = true;
   385     nsresult rv = NS_DispatchToMainThread(this);
   386     NS_ENSURE_SUCCESS(rv, false);
   388     {
   389       MutexAutoLock lock(mMutex);
   390       while (mWaiting) {
   391         mCondVar.Wait();
   392       }
   393     }
   395     if (!mOpened) {
   396       return false;
   397     }
   399     // Now that we're open, we're guarnateed a Close() call. However, we are
   400     // not guarnateed someone is holding an outstanding reference until the File
   401     // is closed, so we do that ourselves and Release() in OnClose().
   402     aCloser->Init(this);
   403     AddRef();
   404     return true;
   405   }
   407   // This method must be called if BlockUntilOpen returns 'true'. AutoClose
   408   // mostly takes care of this. A derived class that implements Close() must
   409   // guarnatee that OnClose() is called (eventually).
   410   virtual void
   411   Close() = 0;
   413 protected:
   414   File()
   415   : mMutex("File::mMutex"),
   416     mCondVar(mMutex, "File::mCondVar"),
   417     mWaiting(false),
   418     mOpened(false)
   419   { }
   421   ~File()
   422   {
   423     MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
   424     MOZ_ASSERT(!mOpened, "OnClose() should have been called");
   425   }
   427   void
   428   OnOpen()
   429   {
   430     Notify(true);
   431   }
   433   void
   434   OnFailure()
   435   {
   436     FileDescriptorHolder::Finish();
   438     Notify(false);
   439   }
   441   void
   442   OnClose()
   443   {
   444     FileDescriptorHolder::Finish();
   446     MOZ_ASSERT(mOpened);
   447     mOpened = false;
   449     // Match the AddRef in BlockUntilOpen(). The main thread event loop still
   450     // holds an outstanding ref which will keep 'this' alive until returning to
   451     // the event loop.
   452     Release();
   453   }
   455 private:
   456   void
   457   Notify(bool aSuccess)
   458   {
   459     MOZ_ASSERT(NS_IsMainThread());
   461     MutexAutoLock lock(mMutex);
   462     MOZ_ASSERT(mWaiting);
   464     mWaiting = false;
   465     mOpened = aSuccess;
   466     mCondVar.Notify();
   467   }
   469   Mutex mMutex;
   470   CondVar mCondVar;
   471   bool mWaiting;
   472   bool mOpened;
   473 };
   475 // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
   476 // that factors out the runnable state machine required to open a cache entry
   477 // that runs in the main process.
   478 class MainProcessRunnable : public virtual FileDescriptorHolder
   479 {
   480 public:
   481   NS_DECL_NSIRUNNABLE
   483   // MainProcessRunnable runnable assumes that the derived class ensures
   484   // (through ref-counting or preconditions) that aPrincipal is kept alive for
   485   // the lifetime of the MainProcessRunnable.
   486   MainProcessRunnable(nsIPrincipal* aPrincipal,
   487                       OpenMode aOpenMode,
   488                       WriteParams aWriteParams)
   489   : mPrincipal(aPrincipal),
   490     mOpenMode(aOpenMode),
   491     mWriteParams(aWriteParams),
   492     mNeedAllowNextSynchronizedOp(false),
   493     mPersistence(quota::PERSISTENCE_TYPE_INVALID),
   494     mState(eInitial)
   495   {
   496     MOZ_ASSERT(IsMainProcess());
   497   }
   499   virtual ~MainProcessRunnable()
   500   {
   501     MOZ_ASSERT(mState == eFinished);
   502     MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
   503   }
   505 protected:
   506   // This method is called by the derived class on the main thread when a
   507   // cache entry has been selected to open.
   508   void
   509   OpenForRead(unsigned aModuleIndex)
   510   {
   511     MOZ_ASSERT(NS_IsMainThread());
   512     MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
   513     MOZ_ASSERT(mOpenMode == eOpenForRead);
   515     mModuleIndex = aModuleIndex;
   516     mState = eReadyToOpenCacheFileForRead;
   517     DispatchToIOThread();
   518   }
   520   // This method is called by the derived class on the main thread when no cache
   521   // entry was found to open. If we just tried a lookup in persistent storage
   522   // then we might still get a hit in temporary storage (for an asm.js module
   523   // that wasn't compiled at install-time).
   524   void
   525   CacheMiss()
   526   {
   527     MOZ_ASSERT(NS_IsMainThread());
   528     MOZ_ASSERT(mState == eFailedToReadMetadata ||
   529                mState == eWaitingToOpenCacheFileForRead);
   530     MOZ_ASSERT(mOpenMode == eOpenForRead);
   532     if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   533       Fail();
   534       return;
   535     }
   537     // Try again with a clean slate. InitOnMainThread will see that mPersistence
   538     // is initialized and switch to temporary storage.
   539     MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
   540     FinishOnMainThread();
   541     mState = eInitial;
   542     NS_DispatchToMainThread(this);
   543   }
   545   // This method is called by the derived class (either on the JS compilation
   546   // thread or the main thread) when the JS engine is finished reading/writing
   547   // the cache entry.
   548   void
   549   Close()
   550   {
   551     MOZ_ASSERT(mState == eOpened);
   552     mState = eClosing;
   553     NS_DispatchToMainThread(this);
   554   }
   556   // This method is called both internally and by derived classes upon any
   557   // failure that prevents the eventual opening of the cache entry.
   558   void
   559   Fail()
   560   {
   561     MOZ_ASSERT(mState != eOpened &&
   562                mState != eClosing &&
   563                mState != eFailing &&
   564                mState != eFinished);
   566     mState = eFailing;
   567     NS_DispatchToMainThread(this);
   568   }
   570   // Called by MainProcessRunnable on the main thread after metadata is open:
   571   virtual void
   572   OnOpenMetadataForRead(const Metadata& aMetadata) = 0;
   574   // Called by MainProcessRunnable on the main thread after the entry is open:
   575   virtual void
   576   OnOpenCacheFile() = 0;
   578   // This method may be overridden, but it must be called from the overrider.
   579   // Called by MainProcessRunnable on the main thread after a call to Fail():
   580   virtual void
   581   OnFailure()
   582   {
   583     FinishOnMainThread();
   584   }
   586   // This method may be overridden, but it must be called from the overrider.
   587   // Called by MainProcessRunnable on the main thread after a call to Close():
   588   virtual void
   589   OnClose()
   590   {
   591     FinishOnMainThread();
   592   }
   594 private:
   595   nsresult
   596   InitOnMainThread();
   598   nsresult
   599   ReadMetadata();
   601   nsresult
   602   OpenCacheFileForWrite();
   604   nsresult
   605   OpenCacheFileForRead();
   607   void
   608   FinishOnMainThread();
   610   void
   611   DispatchToIOThread()
   612   {
   613     // If shutdown just started, the QuotaManager may have been deleted.
   614     QuotaManager* qm = QuotaManager::Get();
   615     if (!qm) {
   616       Fail();
   617       return;
   618     }
   620     nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
   621     if (NS_FAILED(rv)) {
   622       Fail();
   623       return;
   624     }
   625   }
   627   nsIPrincipal* const mPrincipal;
   628   const OpenMode mOpenMode;
   629   const WriteParams mWriteParams;
   631   // State initialized during eInitial:
   632   bool mNeedAllowNextSynchronizedOp;
   633   quota::PersistenceType mPersistence;
   634   nsCString mGroup;
   635   nsCString mOrigin;
   636   nsCString mStorageId;
   638   // State initialized during eReadyToReadMetadata
   639   nsCOMPtr<nsIFile> mDirectory;
   640   nsCOMPtr<nsIFile> mMetadataFile;
   641   Metadata mMetadata;
   643   // State initialized during eWaitingToOpenCacheFileForRead
   644   unsigned mModuleIndex;
   646   enum State {
   647     eInitial, // Just created, waiting to be dispatched to main thread
   648     eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
   649     eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
   650     eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
   651     eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
   652     eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
   653     eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
   654     eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
   655     eOpened, // Finished calling OnOpen, waiting to be closed
   656     eClosing, // Waiting to be dispatched to main thread again
   657     eFailing, // Just failed, waiting to be dispatched to the main thread
   658     eFinished, // Terminal state
   659   };
   660   State mState;
   661 };
   663 nsresult
   664 MainProcessRunnable::InitOnMainThread()
   665 {
   666   MOZ_ASSERT(NS_IsMainThread());
   667   MOZ_ASSERT(mState == eInitial);
   669   QuotaManager* qm = QuotaManager::GetOrCreate();
   670   NS_ENSURE_STATE(qm);
   672   nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup,
   673                                                    &mOrigin, nullptr, nullptr);
   674   NS_ENSURE_SUCCESS(rv, rv);
   676   bool isApp = mPrincipal->GetAppStatus() !=
   677                nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   679   if (mOpenMode == eOpenForWrite) {
   680     MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
   681     if (mWriteParams.mInstalled) {
   682       // If we are performing install-time caching of an app, we'd like to store
   683       // the cache entry in persistent storage so the entry is never evicted,
   684       // but we need to verify that the app has unlimited storage permissions
   685       // first. Unlimited storage permissions justify us in skipping all quota
   686       // checks when storing the cache entry and avoids all the issues around
   687       // the persistent quota prompt.
   688       MOZ_ASSERT(isApp);
   690       nsCOMPtr<nsIPermissionManager> pm =
   691         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   692       NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
   694       uint32_t permission;
   695       rv = pm->TestPermissionFromPrincipal(mPrincipal,
   696                                            PERMISSION_STORAGE_UNLIMITED,
   697                                            &permission);
   698       NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
   700       // If app doens't have the unlimited storage permission, we can still
   701       // cache in temporary for a likely good first-run experience.
   702       mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
   703                      ? quota::PERSISTENCE_TYPE_PERSISTENT
   704                      : quota::PERSISTENCE_TYPE_TEMPORARY;
   705     } else {
   706       mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
   707     }
   708   } else {
   709     // For the reasons described above, apps may have cache entries in both
   710     // persistent and temporary storage. At lookup time we don't know how and
   711     // where the given script was cached, so start the search in persistent
   712     // storage and, if that fails, search in temporary storage. (Non-apps can
   713     // only be stored in temporary storage.)
   714     if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
   715       mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
   716                            : quota::PERSISTENCE_TYPE_TEMPORARY;
   717     } else {
   718       MOZ_ASSERT(isApp);
   719       MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
   720       mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
   721     }
   722   }
   724   QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
   725                              NS_LITERAL_STRING("asmjs"), mStorageId);
   727   return NS_OK;
   728 }
   730 nsresult
   731 MainProcessRunnable::ReadMetadata()
   732 {
   733   AssertIsOnIOThread();
   734   MOZ_ASSERT(mState == eReadyToReadMetadata);
   736   QuotaManager* qm = QuotaManager::Get();
   737   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   739   // Only track quota for temporary storage. For persistent storage, we've
   740   // already checked that we have unlimited-storage permissions.
   741   bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
   743   nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
   744                                               trackQuota,
   745                                               getter_AddRefs(mDirectory));
   746   NS_ENSURE_SUCCESS(rv, rv);
   748   rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
   749   NS_ENSURE_SUCCESS(rv, rv);
   751   bool exists;
   752   rv = mDirectory->Exists(&exists);
   753   NS_ENSURE_SUCCESS(rv, rv);
   755   if (!exists) {
   756     rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
   757     NS_ENSURE_SUCCESS(rv, rv);
   758   } else {
   759     DebugOnly<bool> isDirectory;
   760     MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory)));
   761     MOZ_ASSERT(isDirectory, "Should have caught this earlier!");
   762   }
   764   rv = mDirectory->Clone(getter_AddRefs(mMetadataFile));
   765   NS_ENSURE_SUCCESS(rv, rv);
   767   rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME));
   768   NS_ENSURE_SUCCESS(rv, rv);
   770   rv = mMetadataFile->Exists(&exists);
   771   NS_ENSURE_SUCCESS(rv, rv);
   773   if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) {
   774     exists = false;
   775   }
   777   if (!exists) {
   778     // If we are reading, we can't possibly have a cache hit.
   779     if (mOpenMode == eOpenForRead) {
   780       return NS_ERROR_FILE_NOT_FOUND;
   781     }
   783     // Initialize Metadata with a valid empty state for the LRU cache.
   784     for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
   785       Metadata::Entry& entry = mMetadata.mEntries[i];
   786       entry.mModuleIndex = i;
   787       entry.clear();
   788     }
   789   }
   791   return NS_OK;
   792 }
   794 nsresult
   795 MainProcessRunnable::OpenCacheFileForWrite()
   796 {
   797   AssertIsOnIOThread();
   798   MOZ_ASSERT(mState == eReadyToReadMetadata);
   799   MOZ_ASSERT(mOpenMode == eOpenForWrite);
   801   mFileSize = mWriteParams.mSize;
   803   // Kick out the oldest entry in the LRU queue in the metadata.
   804   mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;
   806   nsCOMPtr<nsIFile> file;
   807   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   808   NS_ENSURE_SUCCESS(rv, rv);
   810   QuotaManager* qm = QuotaManager::Get();
   811   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   813   // If we are allocating in temporary storage, ask the QuotaManager if we're
   814   // within the quota. If we are allocating in persistent storage, we've already
   815   // checked that we have the unlimited-storage permission, so there is nothing
   816   // to check.
   817   if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   818     // Create the QuotaObject before all file IO and keep it alive until caching
   819     // completes to get maximum assertion coverage in QuotaManager against
   820     // concurrent removal, etc.
   821     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
   822     NS_ENSURE_STATE(mQuotaObject);
   824     if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
   825       // If the request fails, it might be because mOrigin is using too much
   826       // space (MaybeAllocateMoreSpace will not evict our own origin since it is
   827       // active). Try to make some space by evicting LRU entries until there is
   828       // enough space.
   829       EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
   830       if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
   831         return NS_ERROR_FAILURE;
   832       }
   833     }
   834   }
   836   int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   837   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   838   NS_ENSURE_SUCCESS(rv, rv);
   840   // Move the mModuleIndex's LRU entry to the recent end of the queue.
   841   PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
   842   Metadata::Entry& entry = mMetadata.mEntries[0];
   843   entry.mFastHash = mWriteParams.mFastHash;
   844   entry.mNumChars = mWriteParams.mNumChars;
   845   entry.mFullHash = mWriteParams.mFullHash;
   846   entry.mModuleIndex = mModuleIndex;
   848   rv = WriteMetadataFile(mMetadataFile, mMetadata);
   849   NS_ENSURE_SUCCESS(rv, rv);
   851   return NS_OK;
   852 }
   854 nsresult
   855 MainProcessRunnable::OpenCacheFileForRead()
   856 {
   857   AssertIsOnIOThread();
   858   MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
   859   MOZ_ASSERT(mOpenMode == eOpenForRead);
   861   nsCOMPtr<nsIFile> file;
   862   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   863   NS_ENSURE_SUCCESS(rv, rv);
   865   QuotaManager* qm = QuotaManager::Get();
   866   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   868   if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   869     // Even though it's not strictly necessary, create the QuotaObject before
   870     // all file IO and keep it alive until caching completes to get maximum
   871     // assertion coverage in QuotaManager against concurrent removal, etc.
   872     mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
   873     NS_ENSURE_STATE(mQuotaObject);
   874   }
   876   rv = file->GetFileSize(&mFileSize);
   877   NS_ENSURE_SUCCESS(rv, rv);
   879   int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
   880   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   881   NS_ENSURE_SUCCESS(rv, rv);
   883   // Move the mModuleIndex's LRU entry to the recent end of the queue.
   884   unsigned lruIndex = 0;
   885   while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
   886     if (++lruIndex == Metadata::kNumEntries) {
   887       return NS_ERROR_UNEXPECTED;
   888     }
   889   }
   890   Metadata::Entry entry = mMetadata.mEntries[lruIndex];
   891   PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
   892   mMetadata.mEntries[0] = entry;
   894   rv = WriteMetadataFile(mMetadataFile, mMetadata);
   895   NS_ENSURE_SUCCESS(rv, rv);
   897   return NS_OK;
   898 }
   900 void
   901 MainProcessRunnable::FinishOnMainThread()
   902 {
   903   MOZ_ASSERT(NS_IsMainThread());
   905   // Per FileDescriptorHolder::Finish()'s comment, call before
   906   // AllowNextSynchronizedOp.
   907   FileDescriptorHolder::Finish();
   909   if (mNeedAllowNextSynchronizedOp) {
   910     mNeedAllowNextSynchronizedOp = false;
   911     QuotaManager* qm = QuotaManager::Get();
   912     if (qm) {
   913       qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
   914                                   Nullable<PersistenceType>(mPersistence),
   915                                   mStorageId);
   916     }
   917   }
   918 }
   920 NS_IMETHODIMP
   921 MainProcessRunnable::Run()
   922 {
   923   nsresult rv;
   925   // All success/failure paths must eventually call Finish() to avoid leaving
   926   // the parser hanging.
   927   switch (mState) {
   928     case eInitial: {
   929       MOZ_ASSERT(NS_IsMainThread());
   931       rv = InitOnMainThread();
   932       if (NS_FAILED(rv)) {
   933         Fail();
   934         return NS_OK;
   935       }
   937       mState = eWaitingToOpenMetadata;
   938       rv = QuotaManager::Get()->WaitForOpenAllowed(
   939                                      OriginOrPatternString::FromOrigin(mOrigin),
   940                                      Nullable<PersistenceType>(mPersistence),
   941                                      mStorageId, this);
   942       if (NS_FAILED(rv)) {
   943         Fail();
   944         return NS_OK;
   945       }
   947       mNeedAllowNextSynchronizedOp = true;
   948       return NS_OK;
   949     }
   951     case eWaitingToOpenMetadata: {
   952       MOZ_ASSERT(NS_IsMainThread());
   954       mState = eReadyToReadMetadata;
   955       DispatchToIOThread();
   956       return NS_OK;
   957     }
   959     case eReadyToReadMetadata: {
   960       AssertIsOnIOThread();
   962       rv = ReadMetadata();
   963       if (NS_FAILED(rv)) {
   964         mState = eFailedToReadMetadata;
   965         NS_DispatchToMainThread(this);
   966         return NS_OK;
   967       }
   969       if (mOpenMode == eOpenForRead) {
   970         mState = eSendingMetadataForRead;
   971         NS_DispatchToMainThread(this);
   972         return NS_OK;
   973       }
   975       rv = OpenCacheFileForWrite();
   976       if (NS_FAILED(rv)) {
   977         Fail();
   978         return NS_OK;
   979       }
   981       mState = eSendingCacheFile;
   982       NS_DispatchToMainThread(this);
   983       return NS_OK;
   984     }
   986     case eFailedToReadMetadata: {
   987       MOZ_ASSERT(NS_IsMainThread());
   989       CacheMiss();
   990       return NS_OK;
   991     }
   993     case eSendingMetadataForRead: {
   994       MOZ_ASSERT(NS_IsMainThread());
   995       MOZ_ASSERT(mOpenMode == eOpenForRead);
   997       mState = eWaitingToOpenCacheFileForRead;
   998       OnOpenMetadataForRead(mMetadata);
   999       return NS_OK;
  1002     case eReadyToOpenCacheFileForRead: {
  1003       AssertIsOnIOThread();
  1004       MOZ_ASSERT(mOpenMode == eOpenForRead);
  1006       rv = OpenCacheFileForRead();
  1007       if (NS_FAILED(rv)) {
  1008         Fail();
  1009         return NS_OK;
  1012       mState = eSendingCacheFile;
  1013       NS_DispatchToMainThread(this);
  1014       return NS_OK;
  1017     case eSendingCacheFile: {
  1018       MOZ_ASSERT(NS_IsMainThread());
  1020       mState = eOpened;
  1021       OnOpenCacheFile();
  1022       return NS_OK;
  1025     case eFailing: {
  1026       MOZ_ASSERT(NS_IsMainThread());
  1028       mState = eFinished;
  1029       OnFailure();
  1030       return NS_OK;
  1033     case eClosing: {
  1034       MOZ_ASSERT(NS_IsMainThread());
  1036       mState = eFinished;
  1037       OnClose();
  1038       return NS_OK;
  1041     case eWaitingToOpenCacheFileForRead:
  1042     case eOpened:
  1043     case eFinished: {
  1044       MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
  1048   MOZ_ASSUME_UNREACHABLE("Corrupt state");
  1049   return NS_OK;
  1052 bool
  1053 FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
  1054               unsigned* aModuleIndex)
  1056   // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
  1057   // also stores an mFastHash of its first sNumFastHashChars so this gives us a
  1058   // fast way to probabilistically determine whether we have a cache hit. We
  1059   // still do a full hash of all the chars before returning the cache file to
  1060   // the engine to avoid penalizing the case where there are multiple cached
  1061   // asm.js modules where the first sNumFastHashChars are the same. The
  1062   // mFullHash of each cache entry can have a different mNumChars so the fast
  1063   // hash allows us to avoid performing up to Metadata::kNumEntries separate
  1064   // full hashes.
  1065   uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
  1066   MOZ_ASSERT(numChars > sNumFastHashChars);
  1067   uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);
  1069   for (unsigned i = 0; i < Metadata::kNumEntries ; i++) {
  1070     // Compare the "fast hash" first to see whether it is worthwhile to
  1071     // hash all the chars.
  1072     Metadata::Entry entry = aMetadata.mEntries[i];
  1073     if (entry.mFastHash != fastHash) {
  1074       continue;
  1077     // Assuming we have enough characters, hash all the chars it would take
  1078     // to match this cache entry and compare to the cache entry. If we get a
  1079     // hit we'll still do a full source match later (in the JS engine), but
  1080     // the full hash match means this is probably the cache entry we want.
  1081     if (numChars < entry.mNumChars) {
  1082       continue;
  1084     uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
  1085     if (entry.mFullHash != fullHash) {
  1086       continue;
  1089     *aModuleIndex = entry.mModuleIndex;
  1090     return true;
  1093   return false;
  1096 // A runnable that executes for a cache access originating in the main process.
  1097 class SingleProcessRunnable MOZ_FINAL : public File,
  1098                                         private MainProcessRunnable
  1100 public:
  1101   // In the single-process case, the calling JS compilation thread holds the
  1102   // nsIPrincipal alive indirectly (via the global object -> compartment ->
  1103   // principal) so we don't have to ref-count it here. This is fortunate since
  1104   // we are off the main thread and nsIPrincipals can only be ref-counted on
  1105   // the main thread.
  1106   SingleProcessRunnable(nsIPrincipal* aPrincipal,
  1107                         OpenMode aOpenMode,
  1108                         WriteParams aWriteParams,
  1109                         ReadParams aReadParams)
  1110   : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
  1111     mReadParams(aReadParams)
  1113     MOZ_ASSERT(IsMainProcess());
  1114     MOZ_ASSERT(!NS_IsMainThread());
  1115     MOZ_COUNT_CTOR(SingleProcessRunnable);
  1118   ~SingleProcessRunnable()
  1120     MOZ_COUNT_DTOR(SingleProcessRunnable);
  1123 private:
  1124   void
  1125   OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1127     uint32_t moduleIndex;
  1128     if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
  1129       MainProcessRunnable::OpenForRead(moduleIndex);
  1130     } else {
  1131       MainProcessRunnable::CacheMiss();
  1135   void
  1136   OnOpenCacheFile() MOZ_OVERRIDE
  1138     File::OnOpen();
  1141   void
  1142   Close() MOZ_OVERRIDE MOZ_FINAL
  1144     MainProcessRunnable::Close();
  1147   void
  1148   OnFailure() MOZ_OVERRIDE
  1150     MainProcessRunnable::OnFailure();
  1151     File::OnFailure();
  1154   void
  1155   OnClose() MOZ_OVERRIDE MOZ_FINAL
  1157     MainProcessRunnable::OnClose();
  1158     File::OnClose();
  1161   // Avoid MSVC 'dominance' warning by having clear Run() override.
  1162   NS_IMETHODIMP
  1163   Run() MOZ_OVERRIDE
  1165     return MainProcessRunnable::Run();
  1168   ReadParams mReadParams;
  1169 };
  1171 // A runnable that executes in a parent process for a cache access originating
  1172 // in the content process. This runnable gets registered as an IPDL subprotocol
  1173 // actor so that it can communicate with the corresponding ChildProcessRunnable.
  1174 class ParentProcessRunnable MOZ_FINAL : public PAsmJSCacheEntryParent,
  1175                                         public MainProcessRunnable
  1177 public:
  1178   // The given principal comes from an IPC::Principal which will be dec-refed
  1179   // at the end of the message, so we must ref-count it here. Fortunately, we
  1180   // are on the main thread (where PContent messages are delivered).
  1181   ParentProcessRunnable(nsIPrincipal* aPrincipal,
  1182                         OpenMode aOpenMode,
  1183                         WriteParams aWriteParams)
  1184   : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
  1185     mPrincipalHolder(aPrincipal),
  1186     mActorDestroyed(false),
  1187     mOpened(false),
  1188     mFinished(false)
  1190     MOZ_ASSERT(IsMainProcess());
  1191     MOZ_ASSERT(NS_IsMainThread());
  1192     MOZ_COUNT_CTOR(ParentProcessRunnable);
  1195 private:
  1196   ~ParentProcessRunnable()
  1198     MOZ_ASSERT(!mPrincipalHolder, "Should have already been released");
  1199     MOZ_ASSERT(mActorDestroyed);
  1200     MOZ_ASSERT(mFinished);
  1201     MOZ_COUNT_DTOR(ParentProcessRunnable);
  1204   bool
  1205   Recv__delete__() MOZ_OVERRIDE
  1207     MOZ_ASSERT(!mFinished);
  1208     mFinished = true;
  1210     if (mOpened) {
  1211       MainProcessRunnable::Close();
  1212     } else {
  1213       MainProcessRunnable::Fail();
  1216     return true;
  1219   void
  1220   ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
  1222     MOZ_ASSERT(!mActorDestroyed);
  1223     mActorDestroyed = true;
  1225     // Assume ActorDestroy can happen at any time, so probe the current state to
  1226     // determine what needs to happen.
  1228     if (mFinished) {
  1229       return;
  1232     mFinished = true;
  1234     if (mOpened) {
  1235       MainProcessRunnable::Close();
  1236     } else {
  1237       MainProcessRunnable::Fail();
  1241   void
  1242   OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1244     MOZ_ASSERT(NS_IsMainThread());
  1246     if (!SendOnOpenMetadataForRead(aMetadata)) {
  1247       unused << Send__delete__(this);
  1251   bool
  1252   RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE
  1254     MainProcessRunnable::OpenForRead(aModuleIndex);
  1255     return true;
  1258   bool
  1259   RecvCacheMiss() MOZ_OVERRIDE
  1261     MainProcessRunnable::CacheMiss();
  1262     return true;
  1265   void
  1266   OnOpenCacheFile() MOZ_OVERRIDE
  1268     MOZ_ASSERT(NS_IsMainThread());
  1270     MOZ_ASSERT(!mOpened);
  1271     mOpened = true;
  1273     FileDescriptor::PlatformHandleType handle =
  1274       FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc));
  1275     if (!SendOnOpenCacheFile(mFileSize, handle)) {
  1276       unused << Send__delete__(this);
  1280   void
  1281   OnClose() MOZ_OVERRIDE MOZ_FINAL
  1283     MOZ_ASSERT(NS_IsMainThread());
  1284     MOZ_ASSERT(mOpened);
  1286     mFinished = true;
  1288     MainProcessRunnable::OnClose();
  1290     MOZ_ASSERT(mActorDestroyed);
  1292     mPrincipalHolder = nullptr;
  1295   void
  1296   OnFailure() MOZ_OVERRIDE
  1298     MOZ_ASSERT(NS_IsMainThread());
  1299     MOZ_ASSERT(!mOpened);
  1301     mFinished = true;
  1303     MainProcessRunnable::OnFailure();
  1305     if (!mActorDestroyed) {
  1306       unused << Send__delete__(this);
  1309     mPrincipalHolder = nullptr;
  1312   nsCOMPtr<nsIPrincipal> mPrincipalHolder;
  1313   bool mActorDestroyed;
  1314   bool mOpened;
  1315   bool mFinished;
  1316 };
  1318 } // unnamed namespace
  1320 PAsmJSCacheEntryParent*
  1321 AllocEntryParent(OpenMode aOpenMode,
  1322                  WriteParams aWriteParams,
  1323                  nsIPrincipal* aPrincipal)
  1325   ParentProcessRunnable* runnable =
  1326     new ParentProcessRunnable(aPrincipal, aOpenMode, aWriteParams);
  1328   // AddRef to keep the runnable alive until DeallocEntryParent.
  1329   runnable->AddRef();
  1331   nsresult rv = NS_DispatchToMainThread(runnable);
  1332   NS_ENSURE_SUCCESS(rv, nullptr);
  1334   return runnable;
  1337 void
  1338 DeallocEntryParent(PAsmJSCacheEntryParent* aActor)
  1340   // Match the AddRef in AllocEntryParent.
  1341   static_cast<ParentProcessRunnable*>(aActor)->Release();
  1344 namespace {
  1346 class ChildProcessRunnable MOZ_FINAL : public File,
  1347                                        public PAsmJSCacheEntryChild
  1349 public:
  1350   NS_DECL_NSIRUNNABLE
  1352   // In the single-process case, the calling JS compilation thread holds the
  1353   // nsIPrincipal alive indirectly (via the global object -> compartment ->
  1354   // principal) so we don't have to ref-count it here. This is fortunate since
  1355   // we are off the main thread and nsIPrincipals can only be ref-counted on
  1356   // the main thread.
  1357   ChildProcessRunnable(nsIPrincipal* aPrincipal,
  1358                        OpenMode aOpenMode,
  1359                        WriteParams aWriteParams,
  1360                        ReadParams aReadParams)
  1361   : mPrincipal(aPrincipal),
  1362     mOpenMode(aOpenMode),
  1363     mWriteParams(aWriteParams),
  1364     mReadParams(aReadParams),
  1365     mActorDestroyed(false),
  1366     mState(eInitial)
  1368     MOZ_ASSERT(!IsMainProcess());
  1369     MOZ_ASSERT(!NS_IsMainThread());
  1370     MOZ_COUNT_CTOR(ChildProcessRunnable);
  1373   ~ChildProcessRunnable()
  1375     MOZ_ASSERT(mState == eFinished);
  1376     MOZ_ASSERT(mActorDestroyed);
  1377     MOZ_COUNT_DTOR(ChildProcessRunnable);
  1380 private:
  1381   bool
  1382   RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1384     MOZ_ASSERT(NS_IsMainThread());
  1385     MOZ_ASSERT(mState == eOpening);
  1387     uint32_t moduleIndex;
  1388     if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
  1389       return SendSelectCacheFileToRead(moduleIndex);
  1392     return SendCacheMiss();
  1395   bool
  1396   RecvOnOpenCacheFile(const int64_t& aFileSize,
  1397                       const FileDescriptor& aFileDesc) MOZ_OVERRIDE
  1399     MOZ_ASSERT(NS_IsMainThread());
  1400     MOZ_ASSERT(mState == eOpening);
  1402     mFileSize = aFileSize;
  1404     mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
  1405     if (!mFileDesc) {
  1406       return false;
  1409     mState = eOpened;
  1410     File::OnOpen();
  1411     return true;
  1414   bool
  1415   Recv__delete__() MOZ_OVERRIDE
  1417     MOZ_ASSERT(NS_IsMainThread());
  1418     MOZ_ASSERT(mState == eOpening);
  1420     Fail();
  1421     return true;
  1424   void
  1425   ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
  1427     MOZ_ASSERT(NS_IsMainThread());
  1428     mActorDestroyed = true;
  1431   void
  1432   Close() MOZ_OVERRIDE MOZ_FINAL
  1434     MOZ_ASSERT(mState == eOpened);
  1436     mState = eClosing;
  1437     NS_DispatchToMainThread(this);
  1440 private:
  1441   void
  1442   Fail()
  1444     MOZ_ASSERT(NS_IsMainThread());
  1445     MOZ_ASSERT(mState == eInitial || mState == eOpening);
  1447     mState = eFinished;
  1448     File::OnFailure();
  1451   nsIPrincipal* const mPrincipal;
  1452   const OpenMode mOpenMode;
  1453   WriteParams mWriteParams;
  1454   ReadParams mReadParams;
  1455   bool mActorDestroyed;
  1457   enum State {
  1458     eInitial, // Just created, waiting to dispatched to the main thread
  1459     eOpening, // Waiting for the parent process to respond
  1460     eOpened, // Parent process opened the entry and sent it back
  1461     eClosing, // Waiting to be dispatched to the main thread to Send__delete__
  1462     eFinished // Terminal state
  1463   };
  1464   State mState;
  1465 };
  1467 NS_IMETHODIMP
  1468 ChildProcessRunnable::Run()
  1470   switch (mState) {
  1471     case eInitial: {
  1472       MOZ_ASSERT(NS_IsMainThread());
  1474       // AddRef to keep this runnable alive until IPDL deallocates the
  1475       // subprotocol (DeallocEntryChild).
  1476       AddRef();
  1478       if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor(
  1479         this, mOpenMode, mWriteParams, IPC::Principal(mPrincipal)))
  1481         // On failure, undo the AddRef (since DeallocEntryChild will not be
  1482         // called) and unblock the parsing thread with a failure. The main
  1483         // thread event loop still holds an outstanding ref which will keep
  1484         // 'this' alive until returning to the event loop.
  1485         Release();
  1487         Fail();
  1488         return NS_OK;
  1491       mState = eOpening;
  1492       return NS_OK;
  1495     case eClosing: {
  1496       MOZ_ASSERT(NS_IsMainThread());
  1498       // Per FileDescriptorHolder::Finish()'s comment, call before
  1499       // AllowNextSynchronizedOp (which happens in the parent upon receipt of
  1500       // the Send__delete__ message).
  1501       File::OnClose();
  1503       if (!mActorDestroyed) {
  1504         unused << Send__delete__(this);
  1507       mState = eFinished;
  1508       return NS_OK;
  1511     case eOpening:
  1512     case eOpened:
  1513     case eFinished: {
  1514       MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
  1518   MOZ_ASSUME_UNREACHABLE("Corrupt state");
  1519   return NS_OK;
  1522 } // unnamed namespace
  1524 void
  1525 DeallocEntryChild(PAsmJSCacheEntryChild* aActor)
  1527   // Match the AddRef before SendPAsmJSCacheEntryConstructor.
  1528   static_cast<ChildProcessRunnable*>(aActor)->Release();
  1531 namespace {
  1533 bool
  1534 OpenFile(nsIPrincipal* aPrincipal,
  1535          OpenMode aOpenMode,
  1536          WriteParams aWriteParams,
  1537          ReadParams aReadParams,
  1538          File::AutoClose* aFile)
  1540   MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
  1541   MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);
  1543   // There are three reasons we don't attempt caching from the main thread:
  1544   //  1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
  1545   //     synchronous waiting on the main thread requiring a runnable to be
  1546   //     dispatched to the main thread.
  1547   //  2. In the child process: the IPDL PContent messages we need to
  1548   //     synchronously wait on are dispatched to the main thread.
  1549   //  3. While a cache lookup *should* be much faster than compilation, IO
  1550   //     operations can be unpredictably slow and we'd like to avoid the
  1551   //     occasional janks on the main thread.
  1552   // We could use a nested event loop to address 1 and 2, but we're potentially
  1553   // in the middle of running JS (eval()) and nested event loops can be
  1554   // semantically observable.
  1555   if (NS_IsMainThread()) {
  1556     return false;
  1559   // If we are in a child process, we need to synchronously call into the
  1560   // parent process to open the file and interact with the QuotaManager. The
  1561   // child can then map the file into its address space to perform I/O.
  1562   nsRefPtr<File> file;
  1563   if (IsMainProcess()) {
  1564     file = new SingleProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
  1565                                      aReadParams);
  1566   } else {
  1567     file = new ChildProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
  1568                                     aReadParams);
  1571   if (!file->BlockUntilOpen(aFile)) {
  1572     return false;
  1575   return file->MapMemory(aOpenMode);
  1578 } // anonymous namespace
  1580 typedef uint32_t AsmJSCookieType;
  1581 static const uint32_t sAsmJSCookie = 0x600d600d;
  1583 bool
  1584 OpenEntryForRead(nsIPrincipal* aPrincipal,
  1585                  const jschar* aBegin,
  1586                  const jschar* aLimit,
  1587                  size_t* aSize,
  1588                  const uint8_t** aMemory,
  1589                  intptr_t* aFile)
  1591   if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
  1592     return false;
  1595   ReadParams readParams;
  1596   readParams.mBegin = aBegin;
  1597   readParams.mLimit = aLimit;
  1599   File::AutoClose file;
  1600   WriteParams notAWrite;
  1601   if (!OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &file)) {
  1602     return false;
  1605   // Although we trust that the stored cache files have not been arbitrarily
  1606   // corrupted, it is possible that a previous execution aborted in the middle
  1607   // of writing a cache file (crash, OOM-killer, etc). To protect against
  1608   // partially-written cache files, we use the following scheme:
  1609   //  - Allocate an extra word at the beginning of every cache file which
  1610   //    starts out 0 (OpenFile opens with PR_TRUNCATE).
  1611   //  - After the asm.js serialization is complete, PR_SyncMemMap to write
  1612   //    everything to disk and then store a non-zero value (sAsmJSCookie)
  1613   //    in the first word.
  1614   //  - When attempting to read a cache file, check whether the first word is
  1615   //    sAsmJSCookie.
  1616   if (file->FileSize() < sizeof(AsmJSCookieType) ||
  1617       *(AsmJSCookieType*)file->MappedMemory() != sAsmJSCookie) {
  1618     return false;
  1621   *aSize = file->FileSize() - sizeof(AsmJSCookieType);
  1622   *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
  1624   // The caller guarnatees a call to CloseEntryForRead (on success or
  1625   // failure) at which point the file will be closed.
  1626   file.Forget(reinterpret_cast<File**>(aFile));
  1627   return true;
  1630 void
  1631 CloseEntryForRead(JS::Handle<JSObject*> global,
  1632                   size_t aSize,
  1633                   const uint8_t* aMemory,
  1634                   intptr_t aFile)
  1636   File::AutoClose file(reinterpret_cast<File*>(aFile));
  1638   MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
  1639   MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
  1642 bool
  1643 OpenEntryForWrite(nsIPrincipal* aPrincipal,
  1644                   bool aInstalled,
  1645                   const jschar* aBegin,
  1646                   const jschar* aEnd,
  1647                   size_t aSize,
  1648                   uint8_t** aMemory,
  1649                   intptr_t* aFile)
  1651   if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
  1652     return false;
  1655   // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
  1656   aSize += sizeof(AsmJSCookieType);
  1658   static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
  1660   WriteParams writeParams;
  1661   writeParams.mInstalled = aInstalled;
  1662   writeParams.mSize = aSize;
  1663   writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
  1664   writeParams.mNumChars = aEnd - aBegin;
  1665   writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
  1667   File::AutoClose file;
  1668   ReadParams notARead;
  1669   if (!OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file)) {
  1670     return false;
  1673   // Strip off the AsmJSCookieType from the buffer returned to the caller,
  1674   // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
  1675   // with a cookie.
  1676   *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
  1678   // The caller guarnatees a call to CloseEntryForWrite (on success or
  1679   // failure) at which point the file will be closed
  1680   file.Forget(reinterpret_cast<File**>(aFile));
  1681   return true;
  1684 void
  1685 CloseEntryForWrite(JS::Handle<JSObject*> global,
  1686                    size_t aSize,
  1687                    uint8_t* aMemory,
  1688                    intptr_t aFile)
  1690   File::AutoClose file(reinterpret_cast<File*>(aFile));
  1692   MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
  1693   MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
  1695   // Flush to disk before writing the cookie (see OpenEntryForRead).
  1696   if (PR_SyncMemMap(file->FileDesc(),
  1697                     file->MappedMemory(),
  1698                     file->FileSize()) == PR_SUCCESS) {
  1699     *(AsmJSCookieType*)file->MappedMemory() = sAsmJSCookie;
  1703 bool
  1704 GetBuildId(JS::BuildIdCharVector* aBuildID)
  1706   nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
  1707   if (!info) {
  1708     return false;
  1711   nsCString buildID;
  1712   nsresult rv = info->GetPlatformBuildID(buildID);
  1713   NS_ENSURE_SUCCESS(rv, false);
  1715   if (!aBuildID->resize(buildID.Length())) {
  1716     return false;
  1719   for (size_t i = 0; i < buildID.Length(); i++) {
  1720     (*aBuildID)[i] = buildID[i];
  1723   return true;
  1726 class Client : public quota::Client
  1728 public:
  1729   NS_IMETHOD_(MozExternalRefCountType)
  1730   AddRef() MOZ_OVERRIDE;
  1732   NS_IMETHOD_(MozExternalRefCountType)
  1733   Release() MOZ_OVERRIDE;
  1735   virtual Type
  1736   GetType() MOZ_OVERRIDE
  1738     return ASMJS;
  1741   virtual nsresult
  1742   InitOrigin(PersistenceType aPersistenceType,
  1743              const nsACString& aGroup,
  1744              const nsACString& aOrigin,
  1745              UsageInfo* aUsageInfo) MOZ_OVERRIDE
  1747     if (!aUsageInfo) {
  1748       return NS_OK;
  1750     return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
  1753   virtual nsresult
  1754   GetUsageForOrigin(PersistenceType aPersistenceType,
  1755                     const nsACString& aGroup,
  1756                     const nsACString& aOrigin,
  1757                     UsageInfo* aUsageInfo) MOZ_OVERRIDE
  1759     QuotaManager* qm = QuotaManager::Get();
  1760     MOZ_ASSERT(qm, "We were being called by the QuotaManager");
  1762     nsCOMPtr<nsIFile> directory;
  1763     nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
  1764                                             getter_AddRefs(directory));
  1765     NS_ENSURE_SUCCESS(rv, rv);
  1766     MOZ_ASSERT(directory, "We're here because the origin directory exists");
  1768     rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
  1769     NS_ENSURE_SUCCESS(rv, rv);
  1771     DebugOnly<bool> exists;
  1772     MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
  1774     nsCOMPtr<nsISimpleEnumerator> entries;
  1775     rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
  1776     NS_ENSURE_SUCCESS(rv, rv);
  1778     bool hasMore;
  1779     while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
  1780            hasMore && !aUsageInfo->Canceled()) {
  1781       nsCOMPtr<nsISupports> entry;
  1782       rv = entries->GetNext(getter_AddRefs(entry));
  1783       NS_ENSURE_SUCCESS(rv, rv);
  1785       nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
  1786       NS_ENSURE_TRUE(file, NS_NOINTERFACE);
  1788       int64_t fileSize;
  1789       rv = file->GetFileSize(&fileSize);
  1790       NS_ENSURE_SUCCESS(rv, rv);
  1792       MOZ_ASSERT(fileSize >= 0, "Negative size?!");
  1794       // Since the client is not explicitly storing files, append to database
  1795       // usage which represents implicit storage allocation.
  1796       aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
  1798     NS_ENSURE_SUCCESS(rv, rv);
  1800     return NS_OK;
  1803   virtual void
  1804   OnOriginClearCompleted(PersistenceType aPersistenceType,
  1805                          const OriginOrPatternString& aOriginOrPattern)
  1806                          MOZ_OVERRIDE
  1807   { }
  1809   virtual void
  1810   ReleaseIOThreadObjects() MOZ_OVERRIDE
  1811   { }
  1813   virtual bool
  1814   IsFileServiceUtilized() MOZ_OVERRIDE
  1816     return false;
  1819   virtual bool
  1820   IsTransactionServiceActivated() MOZ_OVERRIDE
  1822     return false;
  1825   virtual void
  1826   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
  1827                             nsIRunnable* aCallback) MOZ_OVERRIDE
  1829     MOZ_ASSUME_UNREACHABLE("There are no storages");
  1832   virtual void
  1833   AbortTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
  1835     MOZ_ASSUME_UNREACHABLE("There are no storages");
  1838   virtual bool
  1839   HasTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
  1841     return false;
  1844   virtual void
  1845   ShutdownTransactionService() MOZ_OVERRIDE
  1846   { }
  1848 private:
  1849   nsAutoRefCnt mRefCnt;
  1850   NS_DECL_OWNINGTHREAD
  1851 };
  1853 NS_IMPL_ADDREF(asmjscache::Client)
  1854 NS_IMPL_RELEASE(asmjscache::Client)
  1856 quota::Client*
  1857 CreateClient()
  1859   return new Client();
  1862 } // namespace asmjscache
  1863 } // namespace dom
  1864 } // namespace mozilla
  1866 namespace IPC {
  1868 using mozilla::dom::asmjscache::Metadata;
  1869 using mozilla::dom::asmjscache::WriteParams;
  1871 void
  1872 ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam)
  1874   for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1875     const Metadata::Entry& entry = aParam.mEntries[i];
  1876     WriteParam(aMsg, entry.mFastHash);
  1877     WriteParam(aMsg, entry.mNumChars);
  1878     WriteParam(aMsg, entry.mFullHash);
  1879     WriteParam(aMsg, entry.mModuleIndex);
  1883 bool
  1884 ParamTraits<Metadata>::Read(const Message* aMsg, void** aIter,
  1885                             paramType* aResult)
  1887   for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1888     Metadata::Entry& entry = aResult->mEntries[i];
  1889     if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
  1890         !ReadParam(aMsg, aIter, &entry.mNumChars) ||
  1891         !ReadParam(aMsg, aIter, &entry.mFullHash) ||
  1892         !ReadParam(aMsg, aIter, &entry.mModuleIndex))
  1894       return false;
  1897   return true;
  1900 void
  1901 ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog)
  1903   for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1904     const Metadata::Entry& entry = aParam.mEntries[i];
  1905     LogParam(entry.mFastHash, aLog);
  1906     LogParam(entry.mNumChars, aLog);
  1907     LogParam(entry.mFullHash, aLog);
  1908     LogParam(entry.mModuleIndex, aLog);
  1912 void
  1913 ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
  1915   WriteParam(aMsg, aParam.mSize);
  1916   WriteParam(aMsg, aParam.mFastHash);
  1917   WriteParam(aMsg, aParam.mNumChars);
  1918   WriteParam(aMsg, aParam.mFullHash);
  1919   WriteParam(aMsg, aParam.mInstalled);
  1922 bool
  1923 ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
  1924                                paramType* aResult)
  1926   return ReadParam(aMsg, aIter, &aResult->mSize) &&
  1927          ReadParam(aMsg, aIter, &aResult->mFastHash) &&
  1928          ReadParam(aMsg, aIter, &aResult->mNumChars) &&
  1929          ReadParam(aMsg, aIter, &aResult->mFullHash) &&
  1930          ReadParam(aMsg, aIter, &aResult->mInstalled);
  1933 void
  1934 ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
  1936   LogParam(aParam.mSize, aLog);
  1937   LogParam(aParam.mFastHash, aLog);
  1938   LogParam(aParam.mNumChars, aLog);
  1939   LogParam(aParam.mFullHash, aLog);
  1940   LogParam(aParam.mInstalled, aLog);
  1943 } // namespace IPC

mercurial