Bug 963588 - asmjscache: place cache entries apps that request AOT compilation in persistent storage (r=janv)
authorLuke Wagner <luke@mozilla.com>
Wed, 05 Mar 2014 14:47:10 -0600
changeset 189545 a0c4fa6338dc0bd2640429f30de23ceb9ff357ad
parent 189544 afc3d77901231336d611824db94922f0c66124af
child 189546 2fb9cb300800660907179d1a9882eb287bcd6641
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv
bugs963588
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 963588 - asmjscache: place cache entries apps that request AOT compilation in persistent storage (r=janv)
dom/asmjscache/AsmJSCache.cpp
dom/asmjscache/AsmJSCache.h
dom/asmjscache/PAsmJSCacheEntry.ipdl
dom/base/nsJSEnvironment.cpp
dom/quota/CheckQuotaHelper.cpp
dom/quota/QuotaCommon.h
dom/workers/RuntimeService.cpp
js/src/jit/AsmJSModule.cpp
js/src/jsapi.h
js/src/shell/js.cpp
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/unused.h"
 #include "nsIAtom.h"
 #include "nsIFile.h"
+#include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIRunnable.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIThread.h"
 #include "nsIXULAppInfo.h"
 #include "nsJSPrincipals.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
@@ -484,41 +485,68 @@ public:
   // the lifetime of the MainProcessRunnable.
   MainProcessRunnable(nsIPrincipal* aPrincipal,
                       OpenMode aOpenMode,
                       WriteParams aWriteParams)
   : mPrincipal(aPrincipal),
     mOpenMode(aOpenMode),
     mWriteParams(aWriteParams),
     mNeedAllowNextSynchronizedOp(false),
+    mPersistence(quota::PERSISTENCE_TYPE_INVALID),
     mState(eInitial)
   {
     MOZ_ASSERT(IsMainProcess());
   }
 
   virtual ~MainProcessRunnable()
   {
     MOZ_ASSERT(mState == eFinished);
     MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
   }
 
 protected:
-  // This method is called by the derived class (either on the JS compilation
-  // thread or the main thread) when a cache entry has been selected to open.
+  // This method is called by the derived class on the main thread when a
+  // cache entry has been selected to open.
   void
   OpenForRead(unsigned aModuleIndex)
   {
+    MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
     MOZ_ASSERT(mOpenMode == eOpenForRead);
 
     mModuleIndex = aModuleIndex;
     mState = eReadyToOpenCacheFileForRead;
     DispatchToIOThread();
   }
 
+  // This method is called by the derived class on the main thread when no cache
+  // entry was found to open. If we just tried a lookup in persistent storage
+  // then we might still get a hit in temporary storage (for an asm.js module
+  // that wasn't compiled at install-time).
+  void
+  CacheMiss()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mState == eFailedToReadMetadata ||
+               mState == eWaitingToOpenCacheFileForRead);
+    MOZ_ASSERT(mOpenMode == eOpenForRead);
+
+    if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
+      Fail();
+      return;
+    }
+
+    // Try again with a clean slate. InitOnMainThread will see that mPersistence
+    // is initialized and switch to temporary storage.
+    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
+    FinishOnMainThread();
+    mState = eInitial;
+    NS_DispatchToMainThread(this);
+  }
+
   // This method is called by the derived class (either on the JS compilation
   // thread or the main thread) when the JS engine is finished reading/writing
   // the cache entry.
   void
   Close()
   {
     MOZ_ASSERT(mState == eOpened);
     mState = eClosing;
@@ -597,32 +625,34 @@ private:
   }
 
   nsIPrincipal* const mPrincipal;
   const OpenMode mOpenMode;
   const WriteParams mWriteParams;
 
   // State initialized during eInitial:
   bool mNeedAllowNextSynchronizedOp;
+  quota::PersistenceType mPersistence;
   nsCString mGroup;
   nsCString mOrigin;
   nsCString mStorageId;
 
   // State initialized during eReadyToReadMetadata
   nsCOMPtr<nsIFile> mDirectory;
   nsCOMPtr<nsIFile> mMetadataFile;
   Metadata mMetadata;
 
   // State initialized during eWaitingToOpenCacheFileForRead
   unsigned mModuleIndex;
 
   enum State {
     eInitial, // Just created, waiting to be dispatched to main thread
     eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
     eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
+    eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
     eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
     eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
     eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
     eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
     eOpened, // Finished calling OnOpen, waiting to be closed
     eClosing, // Waiting to be dispatched to main thread again
     eFailing, // Just failed, waiting to be dispatched to the main thread
     eFinished, // Terminal state
@@ -638,35 +668,85 @@ MainProcessRunnable::InitOnMainThread()
 
   QuotaManager* qm = QuotaManager::GetOrCreate();
   NS_ENSURE_STATE(qm);
 
   nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup,
                                                    &mOrigin, nullptr, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  QuotaManager::GetStorageId(quota::PERSISTENCE_TYPE_TEMPORARY,
-                             mOrigin, quota::Client::ASMJS,
-                             NS_LITERAL_STRING("asmjs"),
-                             mStorageId);
+  bool isApp = mPrincipal->GetAppStatus() !=
+               nsIPrincipal::APP_STATUS_NOT_INSTALLED;
+
+  if (mOpenMode == eOpenForWrite) {
+    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
+    if (mWriteParams.mInstalled) {
+      // If we are performing install-time caching of an app, we'd like to store
+      // the cache entry in persistent storage so the entry is never evicted,
+      // but we need to verify that the app has unlimited storage permissions
+      // first. Unlimited storage permissions justify us in skipping all quota
+      // checks when storing the cache entry and avoids all the issues around
+      // the persistent quota prompt.
+      MOZ_ASSERT(isApp);
+
+      nsCOMPtr<nsIPermissionManager> pm =
+        do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+      NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
+
+      uint32_t permission;
+      rv = pm->TestPermissionFromPrincipal(mPrincipal,
+                                           PERMISSION_STORAGE_UNLIMITED,
+                                           &permission);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+      // If app doens't have the unlimited storage permission, we can still
+      // cache in temporary for a likely good first-run experience.
+      mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
+                     ? quota::PERSISTENCE_TYPE_PERSISTENT
+                     : quota::PERSISTENCE_TYPE_TEMPORARY;
+    } else {
+      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
+    }
+  } else {
+    // For the reasons described above, apps may have cache entries in both
+    // persistent and temporary storage. At lookup time we don't know how and
+    // where the given script was cached, so start the search in persistent
+    // storage and, if that fails, search in temporary storage. (Non-apps can
+    // only be stored in temporary storage.)
+    if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
+      mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
+                           : quota::PERSISTENCE_TYPE_TEMPORARY;
+    } else {
+      MOZ_ASSERT(isApp);
+      MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
+      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
+    }
+  }
+
+  QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
+                             NS_LITERAL_STRING("asmjs"), mStorageId);
 
   return NS_OK;
 }
 
 nsresult
 MainProcessRunnable::ReadMetadata()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == eReadyToReadMetadata);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  nsresult rv = qm->EnsureOriginIsInitialized(quota::PERSISTENCE_TYPE_TEMPORARY,
-                                              mGroup, mOrigin, true,
+  // Only track quota for temporary storage. For persistent storage, we've
+  // already checked that we have unlimited-storage permissions.
+  bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
+
+  nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
+                                              trackQuota,
                                               getter_AddRefs(mDirectory));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool exists;
   rv = mDirectory->Exists(&exists);
@@ -725,31 +805,36 @@ MainProcessRunnable::OpenCacheFileForWri
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  // Create the QuotaObject before all file IO to get maximum assertion coverage
-  // in QuotaManager against concurrent removal, etc.
-  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY,
-                                    mGroup, mOrigin, file);
-  NS_ENSURE_STATE(mQuotaObject);
+  // If we are allocating in temporary storage, ask the QuotaManager if we're
+  // within the quota. If we are allocating in persistent storage, we've already
+  // checked that we have the unlimited-storage permission, so there is nothing
+  // to check.
+  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
+    // Create the QuotaObject before all file IO and keep it alive until caching
+    // completes to get maximum assertion coverage in QuotaManager against
+    // concurrent removal, etc.
+    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
+    NS_ENSURE_STATE(mQuotaObject);
 
-  // Let the QuotaManager know we're about to consume more storage. The
-  // QuotaManager may veto this or evict other storage. If allocation failed, it
-  // might be because mOrigin is using too much space (MaybeAllocateMoreSpace
-  // will not evict our origin since it is active). Try to make some space by
-  // evicting entries until there is enough space.
-  if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
-    EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
     if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
-      return NS_ERROR_FAILURE;
+      // If the request fails, it might be because mOrigin is using too much
+      // space (MaybeAllocateMoreSpace will not evict our own origin since it is
+      // active). Try to make some space by evicting LRU entries until there is
+      // enough space.
+      EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
+      if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
+        return NS_ERROR_FAILURE;
+      }
     }
   }
 
   int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Move the mModuleIndex's LRU entry to the recent end of the queue.
@@ -775,21 +860,23 @@ MainProcessRunnable::OpenCacheFileForRea
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
 
-  // Create the QuotaObject before all file IO to get maximum assertion coverage
-  // in QuotaManager against concurrent removal, etc.
-  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY,
-                                    mGroup, mOrigin, file);
-  NS_ENSURE_STATE(mQuotaObject);
+  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
+    // Even though it's not strictly necessary, create the QuotaObject before
+    // all file IO and keep it alive until caching completes to get maximum
+    // assertion coverage in QuotaManager against concurrent removal, etc.
+    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
+    NS_ENSURE_STATE(mQuotaObject);
+  }
 
   rv = file->GetFileSize(&mFileSize);
   NS_ENSURE_SUCCESS(rv, rv);
 
   int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
   rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -819,17 +906,18 @@ MainProcessRunnable::FinishOnMainThread(
   // AllowNextSynchronizedOp.
   FileDescriptorHolder::Finish();
 
   if (mNeedAllowNextSynchronizedOp) {
     mNeedAllowNextSynchronizedOp = false;
     QuotaManager* qm = QuotaManager::Get();
     if (qm) {
       qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
-                                  Nullable<PersistenceType>(), mStorageId);
+                                  Nullable<PersistenceType>(mPersistence),
+                                  mStorageId);
     }
   }
 }
 
 NS_IMETHODIMP
 MainProcessRunnable::Run()
 {
   nsresult rv;
@@ -844,18 +932,18 @@ MainProcessRunnable::Run()
       if (NS_FAILED(rv)) {
         Fail();
         return NS_OK;
       }
 
       mState = eWaitingToOpenMetadata;
       rv = QuotaManager::Get()->WaitForOpenAllowed(
                                      OriginOrPatternString::FromOrigin(mOrigin),
-                                     Nullable<PersistenceType>(), mStorageId,
-                                     this);
+                                     Nullable<PersistenceType>(mPersistence),
+                                     mStorageId, this);
       if (NS_FAILED(rv)) {
         Fail();
         return NS_OK;
       }
 
       mNeedAllowNextSynchronizedOp = true;
       return NS_OK;
     }
@@ -868,17 +956,18 @@ MainProcessRunnable::Run()
       return NS_OK;
     }
 
     case eReadyToReadMetadata: {
       AssertIsOnIOThread();
 
       rv = ReadMetadata();
       if (NS_FAILED(rv)) {
-        Fail();
+        mState = eFailedToReadMetadata;
+        NS_DispatchToMainThread(this);
         return NS_OK;
       }
 
       if (mOpenMode == eOpenForRead) {
         mState = eSendingMetadataForRead;
         NS_DispatchToMainThread(this);
         return NS_OK;
       }
@@ -889,16 +978,23 @@ MainProcessRunnable::Run()
         return NS_OK;
       }
 
       mState = eSendingCacheFile;
       NS_DispatchToMainThread(this);
       return NS_OK;
     }
 
+    case eFailedToReadMetadata: {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      CacheMiss();
+      return NS_OK;
+    }
+
     case eSendingMetadataForRead: {
       MOZ_ASSERT(NS_IsMainThread());
       MOZ_ASSERT(mOpenMode == eOpenForRead);
 
       mState = eWaitingToOpenCacheFileForRead;
       OnOpenMetadataForRead(mMetadata);
       return NS_OK;
     }
@@ -1024,22 +1120,21 @@ public:
     MOZ_COUNT_DTOR(SingleProcessRunnable);
   }
 
 private:
   void
   OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
   {
     uint32_t moduleIndex;
-    if (!FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
-      MainProcessRunnable::Fail();
-      return;
+    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
+      MainProcessRunnable::OpenForRead(moduleIndex);
+    } else {
+      MainProcessRunnable::CacheMiss();
     }
-
-    MainProcessRunnable::OpenForRead(moduleIndex);
   }
 
   void
   OnOpenCacheFile() MOZ_OVERRIDE
   {
     File::OnOpen();
   }
 
@@ -1155,16 +1250,23 @@ private:
 
   bool
   RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE
   {
     MainProcessRunnable::OpenForRead(aModuleIndex);
     return true;
   }
 
+  bool
+  RecvCacheMiss() MOZ_OVERRIDE
+  {
+    MainProcessRunnable::CacheMiss();
+    return true;
+  }
+
   void
   OnOpenCacheFile() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     MOZ_ASSERT(!mOpened);
     mOpened = true;
 
@@ -1278,23 +1380,21 @@ public:
 private:
   bool
   RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eOpening);
 
     uint32_t moduleIndex;
-    if (!FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
-      Fail();
-      Send__delete__(this);
-      return true;
+    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
+      return SendSelectCacheFileToRead(moduleIndex);
     }
 
-    return SendSelectCacheFileToRead(moduleIndex);
+    return SendCacheMiss();
   }
 
   bool
   RecvOnOpenCacheFile(const int64_t& aFileSize,
                       const FileDescriptor& aFileDesc) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eOpening);
@@ -1536,32 +1636,34 @@ CloseEntryForRead(JS::Handle<JSObject*> 
   File::AutoClose file(reinterpret_cast<File*>(aFile));
 
   MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
   MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
 }
 
 bool
 OpenEntryForWrite(nsIPrincipal* aPrincipal,
+                  bool aInstalled,
                   const jschar* aBegin,
                   const jschar* aEnd,
                   size_t aSize,
                   uint8_t** aMemory,
                   intptr_t* aFile)
 {
   if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
     return false;
   }
 
   // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
   aSize += sizeof(AsmJSCookieType);
 
   static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
 
   WriteParams writeParams;
+  writeParams.mInstalled = aInstalled;
   writeParams.mSize = aSize;
   writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
   writeParams.mNumChars = aEnd - aBegin;
   writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
 
   File::AutoClose file;
   ReadParams notARead;
   if (!OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file)) {
@@ -1637,16 +1739,19 @@ public:
   }
 
   virtual nsresult
   InitOrigin(PersistenceType aPersistenceType,
              const nsACString& aGroup,
              const nsACString& aOrigin,
              UsageInfo* aUsageInfo) MOZ_OVERRIDE
   {
+    if (!aUsageInfo) {
+      return NS_OK;
+    }
     return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
   }
 
   virtual nsresult
   GetUsageForOrigin(PersistenceType aPersistenceType,
                     const nsACString& aGroup,
                     const nsACString& aOrigin,
                     UsageInfo* aUsageInfo) MOZ_OVERRIDE
@@ -1665,18 +1770,19 @@ public:
 
     DebugOnly<bool> exists;
     MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
 
     nsCOMPtr<nsISimpleEnumerator> entries;
     rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    bool more;
-    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&more))) && more) {
+    bool hasMore;
+    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+           hasMore && !aUsageInfo->Canceled()) {
       nsCOMPtr<nsISupports> entry;
       rv = entries->GetNext(getter_AddRefs(entry));
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
       NS_ENSURE_TRUE(file, NS_NOINTERFACE);
 
       int64_t fileSize;
@@ -1684,16 +1790,17 @@ public:
       NS_ENSURE_SUCCESS(rv, rv);
 
       MOZ_ASSERT(fileSize >= 0, "Negative size?!");
 
       // Since the client is not explicitly storing files, append to database
       // usage which represents implicit storage allocation.
       aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
     }
+    NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const OriginOrPatternString& aOriginOrPattern)
                          MOZ_OVERRIDE
@@ -1804,30 +1911,33 @@ ParamTraits<Metadata>::Log(const paramTy
 
 void
 ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
 {
   WriteParam(aMsg, aParam.mSize);
   WriteParam(aMsg, aParam.mFastHash);
   WriteParam(aMsg, aParam.mNumChars);
   WriteParam(aMsg, aParam.mFullHash);
+  WriteParam(aMsg, aParam.mInstalled);
 }
 
 bool
 ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
                                paramType* aResult)
 {
   return ReadParam(aMsg, aIter, &aResult->mSize) &&
          ReadParam(aMsg, aIter, &aResult->mFastHash) &&
          ReadParam(aMsg, aIter, &aResult->mNumChars) &&
-         ReadParam(aMsg, aIter, &aResult->mFullHash);
+         ReadParam(aMsg, aIter, &aResult->mFullHash) &&
+         ReadParam(aMsg, aIter, &aResult->mInstalled);
 }
 
 void
 ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
 {
   LogParam(aParam.mSize, aLog);
   LogParam(aParam.mFastHash, aLog);
   LogParam(aParam.mNumChars, aLog);
   LogParam(aParam.mFullHash, aLog);
+  LogParam(aParam.mInstalled, aLog);
 }
 
 } // namespace IPC
--- a/dom/asmjscache/AsmJSCache.h
+++ b/dom/asmjscache/AsmJSCache.h
@@ -61,22 +61,24 @@ struct Metadata
 
 // Parameters specific to opening a cache entry for writing
 struct WriteParams
 {
   int64_t mSize;
   int64_t mFastHash;
   int64_t mNumChars;
   int64_t mFullHash;
+  bool mInstalled;
 
   WriteParams()
   : mSize(0),
     mFastHash(0),
     mNumChars(0),
-    mFullHash(0)
+    mFullHash(0),
+    mInstalled(false)
   { }
 };
 
 // Parameters specific to opening a cache entry for reading
 struct ReadParams
 {
   const jschar* mBegin;
   const jschar* mLimit;
@@ -108,16 +110,17 @@ OpenEntryForRead(nsIPrincipal* aPrincipa
                  intptr_t *aHandle);
 void
 CloseEntryForRead(JS::Handle<JSObject*> aGlobal,
                   size_t aSize,
                   const uint8_t* aMemory,
                   intptr_t aHandle);
 bool
 OpenEntryForWrite(nsIPrincipal* aPrincipal,
+                  bool aInstalled,
                   const jschar* aBegin,
                   const jschar* aEnd,
                   size_t aSize,
                   uint8_t** aMemory,
                   intptr_t* aHandle);
 void
 CloseEntryForWrite(JS::Handle<JSObject*> aGlobal,
                    size_t aSize,
--- a/dom/asmjscache/PAsmJSCacheEntry.ipdl
+++ b/dom/asmjscache/PAsmJSCacheEntry.ipdl
@@ -16,16 +16,17 @@ protocol PAsmJSCacheEntry
 
   // When the cache is opened to read, the parent process sends over the
   // origin's Metadata so the child process can select the cache entry to open
   // (based on hash) and notify the parent (via SelectCacheFileToRead).
 child:
   OnOpenMetadataForRead(Metadata metadata);
 parent:
   SelectCacheFileToRead(uint32_t moduleIndex);
+  CacheMiss();
 
 child:
   // Once the cache file has been opened, the child is notified and sent an
   // open file descriptor.
   OnOpenCacheFile(int64_t fileSize, FileDescriptor fileDesc);
 
 both:
   __delete__();
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2982,25 +2982,26 @@ AsmJSCacheOpenEntryForRead(JS::Handle<JS
 {
   nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal);
   return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
                                       aHandle);
 }
 
 static bool
 AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
+                            bool aInstalled,
                             const jschar* aBegin,
                             const jschar* aEnd,
                             size_t aSize,
                             uint8_t** aMemory,
                             intptr_t* aHandle)
 {
   nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal);
-  return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
-                                       aHandle);
+  return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
+                                       aSize, aMemory, aHandle);
 }
 
 static void
 OnLargeAllocationFailure()
 {
   nsCOMPtr<nsIObserverService> os =
     mozilla::services::GetObserverService();
   if (os) {
--- a/dom/quota/CheckQuotaHelper.cpp
+++ b/dom/quota/CheckQuotaHelper.cpp
@@ -16,18 +16,16 @@
 
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/Services.h"
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
-#define PERMISSION_INDEXEDDB_UNLIMITED "indexedDB-unlimited"
-
 #define TOPIC_QUOTA_PROMPT "indexedDB-quota-prompt"
 #define TOPIC_QUOTA_RESPONSE "indexedDB-quota-response"
 #define TOPIC_QUOTA_CANCEL "indexedDB-quota-cancel"
 
 USING_QUOTA_NAMESPACE
 using namespace mozilla::services;
 using mozilla::MutexAutoLock;
 
@@ -124,17 +122,17 @@ CheckQuotaHelper::GetQuotaPermission(nsI
                "Chrome windows shouldn't track quota!");
 
   nsCOMPtr<nsIPermissionManager> pm =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   NS_ENSURE_TRUE(pm, nsIPermissionManager::DENY_ACTION);
 
   uint32_t permission;
   nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal,
-                                                PERMISSION_INDEXEDDB_UNLIMITED,
+                                                PERMISSION_STORAGE_UNLIMITED,
                                                 &permission);
   NS_ENSURE_SUCCESS(rv, nsIPermissionManager::DENY_ACTION);
 
   return permission;
 }
 
 NS_IMPL_ISUPPORTS3(CheckQuotaHelper, nsIRunnable,
                    nsIInterfaceRequestor,
@@ -162,17 +160,17 @@ CheckQuotaHelper::Run()
         nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow);
         NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE);
 
         nsCOMPtr<nsIPermissionManager> permissionManager =
           do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
         NS_ENSURE_STATE(permissionManager);
 
         rv = permissionManager->AddFromPrincipal(sop->GetPrincipal(),
-                                                 PERMISSION_INDEXEDDB_UNLIMITED,
+                                                 PERMISSION_STORAGE_UNLIMITED,
                                                  mPromptResult,
                                                  nsIPermissionManager::EXPIRE_NEVER, 0);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
     else if (mPromptResult == nsIPermissionManager::UNKNOWN_ACTION) {
       uint32_t quota = QuotaManager::GetStorageQuotaMB();
 
--- a/dom/quota/QuotaCommon.h
+++ b/dom/quota/QuotaCommon.h
@@ -16,16 +16,17 @@
 #define BEGIN_QUOTA_NAMESPACE \
   namespace mozilla { namespace dom { namespace quota {
 #define END_QUOTA_NAMESPACE \
   } /* namespace quota */ } /* namespace dom */ } /* namespace mozilla */
 #define USING_QUOTA_NAMESPACE \
   using namespace mozilla::dom::quota;
 
 #define DSSTORE_FILE_NAME ".DS_Store"
+#define PERMISSION_STORAGE_UNLIMITED "indexedDB-unlimited"
 
 BEGIN_QUOTA_NAMESPACE
 
 void
 AssertIsOnIOThread();
 
 void
 AssertCurrentThreadOwnsQuotaMutex();
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -737,29 +737,30 @@ AsmJSCacheOpenEntryForRead(JS::Handle<JS
   }
 
   return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
                                       aHandle);
 }
 
 static bool
 AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
+                            bool aInstalled,
                             const jschar* aBegin,
                             const jschar* aEnd,
                             size_t aSize,
                             uint8_t** aMemory,
                             intptr_t* aHandle)
 {
   nsIPrincipal* principal = GetPrincipalForAsmJSCacheOp();
   if (!principal) {
     return false;
   }
 
-  return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
-                                       aHandle);
+  return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
+                                       aSize, aMemory, aHandle);
 }
 
 struct WorkerThreadRuntimePrivate : public PerThreadAtomCache
 {
   WorkerPrivate* mWorkerPrivate;
 };
 
 JSContext*
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -1211,20 +1211,23 @@ js::StoreAsmJSModuleInCache(AsmJSParser 
                             module.serializedSize();
 
     JS::OpenAsmJSCacheEntryForWriteOp open = cx->asmJSCacheOps().openEntryForWrite;
     if (!open)
         return false;
 
     const jschar *begin = parser.tokenStream.rawBase() + ModuleChars::beginOffset(parser);
     const jschar *end = parser.tokenStream.rawBase() + ModuleChars::endOffset(parser);
+    bool installed = parser.options().installedFile;
 
     ScopedCacheEntryOpenedForWrite entry(cx, serializedSize);
-    if (!open(cx->global(), begin, end, entry.serializedSize, &entry.memory, &entry.handle))
+    if (!open(cx->global(), installed, begin, end, entry.serializedSize,
+              &entry.memory, &entry.handle)) {
         return false;
+    }
 
     uint8_t *cursor = entry.memory;
     cursor = machineId.serialize(cursor);
     cursor = moduleChars.serialize(cursor);
     cursor = module.serialize(cursor);
 
     JS_ASSERT(cursor == entry.memory + serializedSize);
     return true;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3486,16 +3486,17 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
         noScriptRval(false),
         selfHostingMode(false),
         canLazilyParse(true),
         strictOption(false),
         extraWarningsOption(false),
         werrorOption(false),
         asmJSOption(false),
         forceAsync(false),
+        installedFile(false),
         sourcePolicy(SAVE_SOURCE),
         introductionType(nullptr),
         introductionLineno(0),
         introductionOffset(0),
         hasIntroductionInfo(false)
     { }
 
     // Set all POD options (those not requiring reference counts, copies,
@@ -3525,16 +3526,17 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
     bool noScriptRval;
     bool selfHostingMode;
     bool canLazilyParse;
     bool strictOption;
     bool extraWarningsOption;
     bool werrorOption;
     bool asmJSOption;
     bool forceAsync;
+    bool installedFile;  // 'true' iff pre-compiling js file in packaged app
     enum SourcePolicy {
         NO_SOURCE,
         LAZY_SOURCE,
         SAVE_SOURCE
     } sourcePolicy;
 
     // |introductionType| is a statically allocated C string:
     // one of "eval", "Function", or "GeneratorFunction".
@@ -4879,19 +4881,26 @@ typedef void
 /*
  * This callback represents a request by the JS engine to open for writing a
  * cache entry of the given size for the given global and char range containing
  * the just-compiled module. If cache entry space is available, the callback
  * shall return 'true' and return the base address and an opaque file handle as
  * outparams. If the callback returns 'true', the JS engine guarantees a call
  * to CloseAsmJSCacheEntryForWriteOp passing the same base address, size and
  * handle.
+ *
+ * If 'installed' is true, then the cache entry is associated with a permanently
+ * installed JS file (e.g., in a packaged webapp). This information allows the
+ * embedding to store the cache entry in a installed location associated with
+ * the principal of 'global' where it will not be evicted until the associated
+ * installed JS file is removed.
  */
 typedef bool
-(* OpenAsmJSCacheEntryForWriteOp)(HandleObject global, const jschar *begin, const jschar *end,
+(* OpenAsmJSCacheEntryForWriteOp)(HandleObject global, bool installed,
+                                  const jschar *begin, const jschar *end,
                                   size_t size, uint8_t **memory, intptr_t *handle);
 typedef void
 (* CloseAsmJSCacheEntryForWriteOp)(HandleObject global, size_t size, uint8_t *memory,
                                    intptr_t handle);
 
 typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
 
 // Return the buildId (represented as a sequence of characters) associated with
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5346,17 +5346,18 @@ ShellCloseAsmJSCacheEntryForRead(HandleO
 #endif
 
     JS_ASSERT(jsCacheOpened == true);
     jsCacheOpened = false;
     close(handle);
 }
 
 static bool
-ShellOpenAsmJSCacheEntryForWrite(HandleObject global, const jschar *begin, const jschar *end,
+ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed,
+                                 const jschar *begin, const jschar *end,
                                  size_t serializedSize, uint8_t **memoryOut, intptr_t *handleOut)
 {
     if (!jsCachingEnabled || !jsCacheAsmJSPath)
         return false;
 
     // Create the cache directory if it doesn't already exist.
     struct stat dirStat;
     if (stat(jsCacheDir, &dirStat) == 0) {