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"
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"
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"
38 #include "private/pprio.h"
39 #include "mozilla/Services.h"
41 #define ASMJSCACHE_METADATA_FILE_NAME "metadata"
42 #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
44 using mozilla::dom::quota::AssertIsOnIOThread
;
45 using mozilla::dom::quota::OriginOrPatternString
;
46 using mozilla::dom::quota::PersistenceType
;
47 using mozilla::dom::quota::QuotaManager
;
48 using mozilla::dom::quota::QuotaObject
;
49 using mozilla::dom::quota::UsageInfo
;
50 using mozilla::unused
;
51 using mozilla::HashString
;
55 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc
, PRFileDesc
, PR_Close
);
58 namespace asmjscache
{
65 return XRE_GetProcessType() == GeckoProcessType_Default
;
68 // Anything smaller should compile fast enough that caching will just add
70 static const size_t sMinCachedModuleLength
= 10000;
72 // The number of characters to hash into the Metadata::Entry::mFastHash.
73 static const unsigned sNumFastHashChars
= 4096;
76 WriteMetadataFile(nsIFile
* aMetadataFile
, const Metadata
& aMetadata
)
78 int32_t openFlags
= PR_WRONLY
| PR_TRUNCATE
| PR_CREATE_FILE
;
80 JS::BuildIdCharVector buildId
;
81 bool ok
= GetBuildId(&buildId
);
82 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
85 nsresult rv
= aMetadataFile
->OpenNSPRFileDesc(openFlags
, 0644, &fd
.rwget());
86 NS_ENSURE_SUCCESS(rv
, rv
);
88 uint32_t length
= buildId
.length();
89 int32_t bytesWritten
= PR_Write(fd
, &length
, sizeof(length
));
90 NS_ENSURE_TRUE(bytesWritten
== sizeof(length
), NS_ERROR_UNEXPECTED
);
92 bytesWritten
= PR_Write(fd
, buildId
.begin(), length
);
93 NS_ENSURE_TRUE(bytesWritten
== int32_t(length
), NS_ERROR_UNEXPECTED
);
95 bytesWritten
= PR_Write(fd
, &aMetadata
, sizeof(aMetadata
));
96 NS_ENSURE_TRUE(bytesWritten
== sizeof(aMetadata
), NS_ERROR_UNEXPECTED
);
102 ReadMetadataFile(nsIFile
* aMetadataFile
, Metadata
& aMetadata
)
104 int32_t openFlags
= PR_RDONLY
;
107 nsresult rv
= aMetadataFile
->OpenNSPRFileDesc(openFlags
, 0644, &fd
.rwget());
108 NS_ENSURE_SUCCESS(rv
, rv
);
110 // Read the buildid and check that it matches the current buildid
112 JS::BuildIdCharVector currentBuildId
;
113 bool ok
= GetBuildId(¤tBuildId
);
114 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
117 int32_t bytesRead
= PR_Read(fd
, &length
, sizeof(length
));
118 NS_ENSURE_TRUE(bytesRead
== sizeof(length
), NS_ERROR_UNEXPECTED
);
120 NS_ENSURE_TRUE(currentBuildId
.length() == length
, NS_ERROR_UNEXPECTED
);
122 JS::BuildIdCharVector fileBuildId
;
123 ok
= fileBuildId
.resize(length
);
124 NS_ENSURE_TRUE(ok
, NS_ERROR_OUT_OF_MEMORY
);
126 bytesRead
= PR_Read(fd
, fileBuildId
.begin(), length
);
127 NS_ENSURE_TRUE(bytesRead
== int32_t(length
), NS_ERROR_UNEXPECTED
);
129 for (uint32_t i
= 0; i
< length
; i
++) {
130 if (currentBuildId
[i
] != fileBuildId
[i
]) {
131 return NS_ERROR_FAILURE
;
135 // Read the Metadata struct
137 bytesRead
= PR_Read(fd
, &aMetadata
, sizeof(aMetadata
));
138 NS_ENSURE_TRUE(bytesRead
== sizeof(aMetadata
), NS_ERROR_UNEXPECTED
);
144 GetCacheFile(nsIFile
* aDirectory
, unsigned aModuleIndex
, nsIFile
** aCacheFile
)
146 nsCOMPtr
<nsIFile
> cacheFile
;
147 nsresult rv
= aDirectory
->Clone(getter_AddRefs(cacheFile
));
148 NS_ENSURE_SUCCESS(rv
, rv
);
150 nsString cacheFileName
= NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE
);
151 cacheFileName
.AppendInt(aModuleIndex
);
152 rv
= cacheFile
->Append(cacheFileName
);
153 NS_ENSURE_SUCCESS(rv
, rv
);
155 cacheFile
.forget(aCacheFile
);
159 class AutoDecreaseUsageForOrigin
161 const nsACString
& mGroup
;
162 const nsACString
& mOrigin
;
167 AutoDecreaseUsageForOrigin(const nsACString
& aGroup
,
168 const nsACString
& aOrigin
)
175 ~AutoDecreaseUsageForOrigin()
177 AssertIsOnIOThread();
183 QuotaManager
* qm
= QuotaManager::Get();
184 MOZ_ASSERT(qm
, "We are on the QuotaManager's IO thread");
186 qm
->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY
,
187 mGroup
, mOrigin
, mFreed
);
192 EvictEntries(nsIFile
* aDirectory
, const nsACString
& aGroup
,
193 const nsACString
& aOrigin
, uint64_t aNumBytes
,
196 AssertIsOnIOThread();
198 AutoDecreaseUsageForOrigin
usage(aGroup
, aOrigin
);
200 for (int i
= Metadata::kLastEntry
; i
>= 0 && usage
.mFreed
< aNumBytes
; i
--) {
201 Metadata::Entry
& entry
= aMetadata
.mEntries
[i
];
202 unsigned moduleIndex
= entry
.mModuleIndex
;
204 nsCOMPtr
<nsIFile
> file
;
205 nsresult rv
= GetCacheFile(aDirectory
, moduleIndex
, getter_AddRefs(file
));
206 if (NS_WARN_IF(NS_FAILED(rv
))) {
211 rv
= file
->Exists(&exists
);
212 if (NS_WARN_IF(NS_FAILED(rv
))) {
218 rv
= file
->GetFileSize(&fileSize
);
219 if (NS_WARN_IF(NS_FAILED(rv
))) {
223 rv
= file
->Remove(false);
224 if (NS_WARN_IF(NS_FAILED(rv
))) {
228 usage
.mFreed
+= fileSize
;
235 // FileDescriptorHolder owns a file descriptor and its memory mapping.
236 // FileDescriptorHolder is derived by all three runnable classes (that is,
237 // (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds,
238 // FileDescriptorHolder is derived virtually by File and MainProcessRunnable for
239 // the benefit of SingleProcessRunnable, which derives both. Since File and
240 // MainProcessRunnable both need to be runnables, FileDescriptorHolder also
241 // derives nsRunnable.
242 class FileDescriptorHolder
: public nsRunnable
245 FileDescriptorHolder()
246 : mQuotaObject(nullptr),
247 mFileSize(INT64_MIN
),
250 mMappedMemory(nullptr)
253 ~FileDescriptorHolder()
255 // These resources should have already been released by Finish().
256 MOZ_ASSERT(!mQuotaObject
);
257 MOZ_ASSERT(!mMappedMemory
);
258 MOZ_ASSERT(!mFileMap
);
259 MOZ_ASSERT(!mFileDesc
);
265 MOZ_ASSERT(mFileSize
>= 0, "Accessing FileSize of unopened file");
272 MOZ_ASSERT(mFileDesc
, "Accessing FileDesc of unopened file");
277 MapMemory(OpenMode aOpenMode
)
279 MOZ_ASSERT(!mFileMap
, "Cannot call MapMemory twice");
281 PRFileMapProtect mapFlags
= aOpenMode
== eOpenForRead
? PR_PROT_READONLY
284 mFileMap
= PR_CreateFileMap(mFileDesc
, mFileSize
, mapFlags
);
285 NS_ENSURE_TRUE(mFileMap
, false);
287 mMappedMemory
= PR_MemMap(mFileMap
, 0, mFileSize
);
288 NS_ENSURE_TRUE(mMappedMemory
, false);
296 MOZ_ASSERT(mMappedMemory
, "Accessing MappedMemory of un-mapped file");
297 return mMappedMemory
;
301 // This method must be called before AllowNextSynchronizedOp (which releases
302 // the lock protecting these resources). It is idempotent, so it is ok to call
303 // multiple times (or before the file has been fully opened).
308 PR_MemUnmap(mMappedMemory
, mFileSize
);
309 mMappedMemory
= nullptr;
312 PR_CloseFileMap(mFileMap
);
320 // Holding the QuotaObject alive until all the cache files are closed enables
321 // assertions in QuotaManager that the cache entry isn't cleared while we
322 // are working on it.
323 mQuotaObject
= nullptr;
326 nsRefPtr
<QuotaObject
> mQuotaObject
;
328 PRFileDesc
* mFileDesc
;
333 // File is a base class shared by (Single|Client)ProcessEntryRunnable that
334 // presents a single interface to the AsmJSCache ops which need to wait until
335 // the file is open, regardless of whether we are executing in the main process
337 class File
: public virtual FileDescriptorHolder
345 explicit AutoClose(File
* aFile
= nullptr)
357 operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN
379 BlockUntilOpen(AutoClose
* aCloser
)
381 MOZ_ASSERT(!mWaiting
, "Can only call BlockUntilOpen once");
382 MOZ_ASSERT(!mOpened
, "Can only call BlockUntilOpen once");
386 nsresult rv
= NS_DispatchToMainThread(this);
387 if (NS_WARN_IF(NS_FAILED(rv
))) {
388 return JS::AsmJSCache_InternalError
;
392 MutexAutoLock
lock(mMutex
);
402 // Now that we're open, we're guarnateed a Close() call. However, we are
403 // not guarnateed someone is holding an outstanding reference until the File
404 // is closed, so we do that ourselves and Release() in OnClose().
407 return JS::AsmJSCache_Success
;
410 // This method must be called if BlockUntilOpen returns 'true'. AutoClose
411 // mostly takes care of this. A derived class that implements Close() must
412 // guarnatee that OnClose() is called (eventually).
418 : mMutex("File::mMutex"),
419 mCondVar(mMutex
, "File::mCondVar"),
422 mResult(JS::AsmJSCache_InternalError
)
427 MOZ_ASSERT(!mWaiting
, "Shouldn't be destroyed while thread is waiting");
428 MOZ_ASSERT(!mOpened
, "OnClose() should have been called");
434 Notify(JS::AsmJSCache_Success
);
438 OnFailure(JS::AsmJSCacheResult aResult
)
440 MOZ_ASSERT(aResult
!= JS::AsmJSCache_Success
);
442 FileDescriptorHolder::Finish();
449 FileDescriptorHolder::Finish();
454 // Match the AddRef in BlockUntilOpen(). The main thread event loop still
455 // holds an outstanding ref which will keep 'this' alive until returning to
462 Notify(JS::AsmJSCacheResult aResult
)
464 MOZ_ASSERT(NS_IsMainThread());
466 MutexAutoLock
lock(mMutex
);
467 MOZ_ASSERT(mWaiting
);
470 mOpened
= aResult
== JS::AsmJSCache_Success
;
479 JS::AsmJSCacheResult mResult
;
482 // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
483 // that factors out the runnable state machine required to open a cache entry
484 // that runs in the main process.
485 class MainProcessRunnable
: public virtual FileDescriptorHolder
490 // MainProcessRunnable runnable assumes that the derived class ensures
491 // (through ref-counting or preconditions) that aPrincipal is kept alive for
492 // the lifetime of the MainProcessRunnable.
493 MainProcessRunnable(nsIPrincipal
* aPrincipal
,
495 WriteParams aWriteParams
)
496 : mPrincipal(aPrincipal
),
497 mOpenMode(aOpenMode
),
498 mWriteParams(aWriteParams
),
499 mNeedAllowNextSynchronizedOp(false),
500 mPersistence(quota::PERSISTENCE_TYPE_INVALID
),
502 mResult(JS::AsmJSCache_InternalError
),
504 mHasUnlimStoragePerm(false),
505 mEnforcingQuota(true)
507 MOZ_ASSERT(IsMainProcess());
510 virtual ~MainProcessRunnable()
512 MOZ_ASSERT(mState
== eFinished
);
513 MOZ_ASSERT(!mNeedAllowNextSynchronizedOp
);
517 // This method is called by the derived class on the main thread when a
518 // cache entry has been selected to open.
520 OpenForRead(unsigned aModuleIndex
)
522 MOZ_ASSERT(NS_IsMainThread());
523 MOZ_ASSERT(mState
== eWaitingToOpenCacheFileForRead
);
524 MOZ_ASSERT(mOpenMode
== eOpenForRead
);
526 mModuleIndex
= aModuleIndex
;
527 mState
= eReadyToOpenCacheFileForRead
;
528 DispatchToIOThread();
531 // This method is called by the derived class on the main thread when no cache
532 // entry was found to open. If we just tried a lookup in persistent storage
533 // then we might still get a hit in temporary storage (for an asm.js module
534 // that wasn't compiled at install-time).
538 MOZ_ASSERT(NS_IsMainThread());
539 MOZ_ASSERT(mState
== eFailedToReadMetadata
||
540 mState
== eWaitingToOpenCacheFileForRead
);
541 MOZ_ASSERT(mOpenMode
== eOpenForRead
);
543 if (mPersistence
== quota::PERSISTENCE_TYPE_TEMPORARY
) {
548 // Try again with a clean slate. InitOnMainThread will see that mPersistence
549 // is initialized and switch to temporary storage.
550 MOZ_ASSERT(mPersistence
== quota::PERSISTENCE_TYPE_PERSISTENT
);
551 FinishOnMainThread();
553 NS_DispatchToMainThread(this);
556 // This method is called by the derived class (either on the JS compilation
557 // thread or the main thread) when the JS engine is finished reading/writing
562 MOZ_ASSERT(mState
== eOpened
);
564 NS_DispatchToMainThread(this);
567 // This method is called both internally and by derived classes upon any
568 // failure that prevents the eventual opening of the cache entry.
572 MOZ_ASSERT(mState
!= eOpened
&&
573 mState
!= eClosing
&&
574 mState
!= eFailing
&&
575 mState
!= eFinished
);
578 NS_DispatchToMainThread(this);
581 // Called by MainProcessRunnable on the main thread after metadata is open:
583 OnOpenMetadataForRead(const Metadata
& aMetadata
) = 0;
585 // Called by MainProcessRunnable on the main thread after the entry is open:
587 OnOpenCacheFile() = 0;
589 // This method may be overridden, but it must be called from the overrider.
590 // Called by MainProcessRunnable on the main thread after a call to Fail():
592 OnFailure(JS::AsmJSCacheResult aResult
)
594 FinishOnMainThread();
597 // This method may be overridden, but it must be called from the overrider.
598 // Called by MainProcessRunnable on the main thread after a call to Close():
602 FinishOnMainThread();
607 InitPersistenceType();
616 OpenCacheFileForWrite();
619 OpenCacheFileForRead();
622 FinishOnMainThread();
627 // If shutdown just started, the QuotaManager may have been deleted.
628 QuotaManager
* qm
= QuotaManager::Get();
634 nsresult rv
= qm
->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL
);
641 nsIPrincipal
* const mPrincipal
;
642 const OpenMode mOpenMode
;
643 const WriteParams mWriteParams
;
645 // State initialized during eInitial:
646 bool mNeedAllowNextSynchronizedOp
;
647 quota::PersistenceType mPersistence
;
650 nsCString mStorageId
;
652 // State initialized during eReadyToReadMetadata
653 nsCOMPtr
<nsIFile
> mDirectory
;
654 nsCOMPtr
<nsIFile
> mMetadataFile
;
657 // State initialized during eWaitingToOpenCacheFileForRead
658 unsigned mModuleIndex
;
661 eInitial
, // Just created, waiting to be dispatched to main thread
662 eWaitingToOpenMetadata
, // Waiting to be called back from WaitForOpenAllowed
663 eReadyToReadMetadata
, // Waiting to read the metadata file on the IO thread
664 eFailedToReadMetadata
, // Waiting to be dispatched to main thread after fail
665 eSendingMetadataForRead
, // Waiting to send OnOpenMetadataForRead
666 eWaitingToOpenCacheFileForRead
, // Waiting to hear back from child
667 eReadyToOpenCacheFileForRead
, // Waiting to open cache file for read
668 eSendingCacheFile
, // Waiting to send OnOpenCacheFile on the main thread
669 eOpened
, // Finished calling OnOpen, waiting to be closed
670 eClosing
, // Waiting to be dispatched to main thread again
671 eFailing
, // Just failed, waiting to be dispatched to the main thread
672 eFinished
, // Terminal state
675 JS::AsmJSCacheResult mResult
;
678 bool mHasUnlimStoragePerm
;
679 bool mEnforcingQuota
;
683 MainProcessRunnable::InitPersistenceType()
685 MOZ_ASSERT(NS_IsMainThread());
686 MOZ_ASSERT(mState
== eInitial
);
688 if (mOpenMode
== eOpenForWrite
) {
689 MOZ_ASSERT(mPersistence
== quota::PERSISTENCE_TYPE_INVALID
);
691 // If we are performing install-time caching of an app, we'd like to store
692 // the cache entry in persistent storage so the entry is never evicted,
693 // but we need to check that quota is not enforced for the app.
694 // That justifies us in skipping all quota checks when storing the cache
695 // entry and avoids all the issues around the persistent quota prompt.
696 // If quota is enforced for the app, then we can still cache in temporary
697 // for a likely good first-run experience.
699 MOZ_ASSERT_IF(mWriteParams
.mInstalled
, mIsApp
);
701 if (mWriteParams
.mInstalled
&&
702 !QuotaManager::IsQuotaEnforced(quota::PERSISTENCE_TYPE_PERSISTENT
,
703 mOrigin
, mIsApp
, mHasUnlimStoragePerm
)) {
704 mPersistence
= quota::PERSISTENCE_TYPE_PERSISTENT
;
706 mPersistence
= quota::PERSISTENCE_TYPE_TEMPORARY
;
712 // For the reasons described above, apps may have cache entries in both
713 // persistent and temporary storage. At lookup time we don't know how and
714 // where the given script was cached, so start the search in persistent
715 // storage and, if that fails, search in temporary storage. (Non-apps can
716 // only be stored in temporary storage.)
718 MOZ_ASSERT_IF(mPersistence
!= quota::PERSISTENCE_TYPE_INVALID
,
719 mIsApp
&& mPersistence
== quota::PERSISTENCE_TYPE_PERSISTENT
);
721 if (mPersistence
== quota::PERSISTENCE_TYPE_INVALID
&& mIsApp
) {
722 mPersistence
= quota::PERSISTENCE_TYPE_PERSISTENT
;
724 mPersistence
= quota::PERSISTENCE_TYPE_TEMPORARY
;
729 MainProcessRunnable::InitOnMainThread()
731 MOZ_ASSERT(NS_IsMainThread());
732 MOZ_ASSERT(mState
== eInitial
);
734 QuotaManager
* qm
= QuotaManager::GetOrCreate();
738 QuotaManager::GetInfoFromPrincipal(mPrincipal
, &mGroup
, &mOrigin
, &mIsApp
,
739 &mHasUnlimStoragePerm
);
740 NS_ENSURE_SUCCESS(rv
, rv
);
742 InitPersistenceType();
745 QuotaManager::IsQuotaEnforced(mPersistence
, mOrigin
, mIsApp
,
746 mHasUnlimStoragePerm
);
748 QuotaManager::GetStorageId(mPersistence
, mOrigin
, quota::Client::ASMJS
,
749 NS_LITERAL_STRING("asmjs"), mStorageId
);
755 MainProcessRunnable::ReadMetadata()
757 AssertIsOnIOThread();
758 MOZ_ASSERT(mState
== eReadyToReadMetadata
);
760 QuotaManager
* qm
= QuotaManager::Get();
761 MOZ_ASSERT(qm
, "We are on the QuotaManager's IO thread");
764 qm
->EnsureOriginIsInitialized(mPersistence
, mGroup
, mOrigin
, mIsApp
,
765 mHasUnlimStoragePerm
,
766 getter_AddRefs(mDirectory
));
767 NS_ENSURE_SUCCESS(rv
, rv
);
769 rv
= mDirectory
->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME
));
770 NS_ENSURE_SUCCESS(rv
, rv
);
773 rv
= mDirectory
->Exists(&exists
);
774 NS_ENSURE_SUCCESS(rv
, rv
);
777 rv
= mDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
778 NS_ENSURE_SUCCESS(rv
, rv
);
780 DebugOnly
<bool> isDirectory
;
781 MOZ_ASSERT(NS_SUCCEEDED(mDirectory
->IsDirectory(&isDirectory
)));
782 MOZ_ASSERT(isDirectory
, "Should have caught this earlier!");
785 rv
= mDirectory
->Clone(getter_AddRefs(mMetadataFile
));
786 NS_ENSURE_SUCCESS(rv
, rv
);
788 rv
= mMetadataFile
->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME
));
789 NS_ENSURE_SUCCESS(rv
, rv
);
791 rv
= mMetadataFile
->Exists(&exists
);
792 NS_ENSURE_SUCCESS(rv
, rv
);
794 if (exists
&& NS_FAILED(ReadMetadataFile(mMetadataFile
, mMetadata
))) {
799 // If we are reading, we can't possibly have a cache hit.
800 if (mOpenMode
== eOpenForRead
) {
801 return NS_ERROR_FILE_NOT_FOUND
;
804 // Initialize Metadata with a valid empty state for the LRU cache.
805 for (unsigned i
= 0; i
< Metadata::kNumEntries
; i
++) {
806 Metadata::Entry
& entry
= mMetadata
.mEntries
[i
];
807 entry
.mModuleIndex
= i
;
816 MainProcessRunnable::OpenCacheFileForWrite()
818 AssertIsOnIOThread();
819 MOZ_ASSERT(mState
== eReadyToReadMetadata
);
820 MOZ_ASSERT(mOpenMode
== eOpenForWrite
);
822 mFileSize
= mWriteParams
.mSize
;
824 // Kick out the oldest entry in the LRU queue in the metadata.
825 mModuleIndex
= mMetadata
.mEntries
[Metadata::kLastEntry
].mModuleIndex
;
827 nsCOMPtr
<nsIFile
> file
;
828 nsresult rv
= GetCacheFile(mDirectory
, mModuleIndex
, getter_AddRefs(file
));
829 NS_ENSURE_SUCCESS(rv
, rv
);
831 QuotaManager
* qm
= QuotaManager::Get();
832 MOZ_ASSERT(qm
, "We are on the QuotaManager's IO thread");
834 if (mEnforcingQuota
) {
835 // Create the QuotaObject before all file IO and keep it alive until caching
836 // completes to get maximum assertion coverage in QuotaManager against
837 // concurrent removal, etc.
838 mQuotaObject
= qm
->GetQuotaObject(mPersistence
, mGroup
, mOrigin
, file
);
839 NS_ENSURE_STATE(mQuotaObject
);
841 if (!mQuotaObject
->MaybeAllocateMoreSpace(0, mWriteParams
.mSize
)) {
842 // If the request fails, it might be because mOrigin is using too much
843 // space (MaybeAllocateMoreSpace will not evict our own origin since it is
844 // active). Try to make some space by evicting LRU entries until there is
846 EvictEntries(mDirectory
, mGroup
, mOrigin
, mWriteParams
.mSize
, mMetadata
);
847 if (!mQuotaObject
->MaybeAllocateMoreSpace(0, mWriteParams
.mSize
)) {
848 mResult
= JS::AsmJSCache_QuotaExceeded
;
849 return NS_ERROR_FAILURE
;
854 int32_t openFlags
= PR_RDWR
| PR_TRUNCATE
| PR_CREATE_FILE
;
855 rv
= file
->OpenNSPRFileDesc(openFlags
, 0644, &mFileDesc
);
856 NS_ENSURE_SUCCESS(rv
, rv
);
858 // Move the mModuleIndex's LRU entry to the recent end of the queue.
859 PodMove(mMetadata
.mEntries
+ 1, mMetadata
.mEntries
, Metadata::kLastEntry
);
860 Metadata::Entry
& entry
= mMetadata
.mEntries
[0];
861 entry
.mFastHash
= mWriteParams
.mFastHash
;
862 entry
.mNumChars
= mWriteParams
.mNumChars
;
863 entry
.mFullHash
= mWriteParams
.mFullHash
;
864 entry
.mModuleIndex
= mModuleIndex
;
866 rv
= WriteMetadataFile(mMetadataFile
, mMetadata
);
867 NS_ENSURE_SUCCESS(rv
, rv
);
873 MainProcessRunnable::OpenCacheFileForRead()
875 AssertIsOnIOThread();
876 MOZ_ASSERT(mState
== eReadyToOpenCacheFileForRead
);
877 MOZ_ASSERT(mOpenMode
== eOpenForRead
);
879 nsCOMPtr
<nsIFile
> file
;
880 nsresult rv
= GetCacheFile(mDirectory
, mModuleIndex
, getter_AddRefs(file
));
881 NS_ENSURE_SUCCESS(rv
, rv
);
883 QuotaManager
* qm
= QuotaManager::Get();
884 MOZ_ASSERT(qm
, "We are on the QuotaManager's IO thread");
886 if (mEnforcingQuota
) {
887 // Even though it's not strictly necessary, create the QuotaObject before
888 // all file IO and keep it alive until caching completes to get maximum
889 // assertion coverage in QuotaManager against concurrent removal, etc.
890 mQuotaObject
= qm
->GetQuotaObject(mPersistence
, mGroup
, mOrigin
, file
);
891 NS_ENSURE_STATE(mQuotaObject
);
894 rv
= file
->GetFileSize(&mFileSize
);
895 NS_ENSURE_SUCCESS(rv
, rv
);
897 int32_t openFlags
= PR_RDONLY
| nsIFile::OS_READAHEAD
;
898 rv
= file
->OpenNSPRFileDesc(openFlags
, 0644, &mFileDesc
);
899 NS_ENSURE_SUCCESS(rv
, rv
);
901 // Move the mModuleIndex's LRU entry to the recent end of the queue.
902 unsigned lruIndex
= 0;
903 while (mMetadata
.mEntries
[lruIndex
].mModuleIndex
!= mModuleIndex
) {
904 if (++lruIndex
== Metadata::kNumEntries
) {
905 return NS_ERROR_UNEXPECTED
;
908 Metadata::Entry entry
= mMetadata
.mEntries
[lruIndex
];
909 PodMove(mMetadata
.mEntries
+ 1, mMetadata
.mEntries
, lruIndex
);
910 mMetadata
.mEntries
[0] = entry
;
912 rv
= WriteMetadataFile(mMetadataFile
, mMetadata
);
913 NS_ENSURE_SUCCESS(rv
, rv
);
919 MainProcessRunnable::FinishOnMainThread()
921 MOZ_ASSERT(NS_IsMainThread());
923 // Per FileDescriptorHolder::Finish()'s comment, call before
924 // AllowNextSynchronizedOp.
925 FileDescriptorHolder::Finish();
927 if (mNeedAllowNextSynchronizedOp
) {
928 mNeedAllowNextSynchronizedOp
= false;
929 QuotaManager
* qm
= QuotaManager::Get();
931 qm
->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin
),
932 Nullable
<PersistenceType
>(mPersistence
),
939 MainProcessRunnable::Run()
943 // All success/failure paths must eventually call Finish() to avoid leaving
944 // the parser hanging.
947 MOZ_ASSERT(NS_IsMainThread());
949 rv
= InitOnMainThread();
955 mState
= eWaitingToOpenMetadata
;
956 rv
= QuotaManager::Get()->WaitForOpenAllowed(
957 OriginOrPatternString::FromOrigin(mOrigin
),
958 Nullable
<PersistenceType
>(mPersistence
),
965 mNeedAllowNextSynchronizedOp
= true;
969 case eWaitingToOpenMetadata
: {
970 MOZ_ASSERT(NS_IsMainThread());
972 mState
= eReadyToReadMetadata
;
973 DispatchToIOThread();
977 case eReadyToReadMetadata
: {
978 AssertIsOnIOThread();
982 mState
= eFailedToReadMetadata
;
983 NS_DispatchToMainThread(this);
987 if (mOpenMode
== eOpenForRead
) {
988 mState
= eSendingMetadataForRead
;
989 NS_DispatchToMainThread(this);
993 rv
= OpenCacheFileForWrite();
999 mState
= eSendingCacheFile
;
1000 NS_DispatchToMainThread(this);
1004 case eFailedToReadMetadata
: {
1005 MOZ_ASSERT(NS_IsMainThread());
1011 case eSendingMetadataForRead
: {
1012 MOZ_ASSERT(NS_IsMainThread());
1013 MOZ_ASSERT(mOpenMode
== eOpenForRead
);
1015 mState
= eWaitingToOpenCacheFileForRead
;
1016 OnOpenMetadataForRead(mMetadata
);
1020 case eReadyToOpenCacheFileForRead
: {
1021 AssertIsOnIOThread();
1022 MOZ_ASSERT(mOpenMode
== eOpenForRead
);
1024 rv
= OpenCacheFileForRead();
1025 if (NS_FAILED(rv
)) {
1030 mState
= eSendingCacheFile
;
1031 NS_DispatchToMainThread(this);
1035 case eSendingCacheFile
: {
1036 MOZ_ASSERT(NS_IsMainThread());
1044 MOZ_ASSERT(NS_IsMainThread());
1052 MOZ_ASSERT(NS_IsMainThread());
1059 case eWaitingToOpenCacheFileForRead
:
1062 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
1066 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
1071 FindHashMatch(const Metadata
& aMetadata
, const ReadParams
& aReadParams
,
1072 unsigned* aModuleIndex
)
1074 // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
1075 // also stores an mFastHash of its first sNumFastHashChars so this gives us a
1076 // fast way to probabilistically determine whether we have a cache hit. We
1077 // still do a full hash of all the chars before returning the cache file to
1078 // the engine to avoid penalizing the case where there are multiple cached
1079 // asm.js modules where the first sNumFastHashChars are the same. The
1080 // mFullHash of each cache entry can have a different mNumChars so the fast
1081 // hash allows us to avoid performing up to Metadata::kNumEntries separate
1083 uint32_t numChars
= aReadParams
.mLimit
- aReadParams
.mBegin
;
1084 MOZ_ASSERT(numChars
> sNumFastHashChars
);
1085 uint32_t fastHash
= HashString(aReadParams
.mBegin
, sNumFastHashChars
);
1087 for (unsigned i
= 0; i
< Metadata::kNumEntries
; i
++) {
1088 // Compare the "fast hash" first to see whether it is worthwhile to
1089 // hash all the chars.
1090 Metadata::Entry entry
= aMetadata
.mEntries
[i
];
1091 if (entry
.mFastHash
!= fastHash
) {
1095 // Assuming we have enough characters, hash all the chars it would take
1096 // to match this cache entry and compare to the cache entry. If we get a
1097 // hit we'll still do a full source match later (in the JS engine), but
1098 // the full hash match means this is probably the cache entry we want.
1099 if (numChars
< entry
.mNumChars
) {
1102 uint32_t fullHash
= HashString(aReadParams
.mBegin
, entry
.mNumChars
);
1103 if (entry
.mFullHash
!= fullHash
) {
1107 *aModuleIndex
= entry
.mModuleIndex
;
1114 // A runnable that executes for a cache access originating in the main process.
1115 class SingleProcessRunnable MOZ_FINAL
: public File
,
1116 private MainProcessRunnable
1119 // In the single-process case, the calling JS compilation thread holds the
1120 // nsIPrincipal alive indirectly (via the global object -> compartment ->
1121 // principal) so we don't have to ref-count it here. This is fortunate since
1122 // we are off the main thread and nsIPrincipals can only be ref-counted on
1124 SingleProcessRunnable(nsIPrincipal
* aPrincipal
,
1126 WriteParams aWriteParams
,
1127 ReadParams aReadParams
)
1128 : MainProcessRunnable(aPrincipal
, aOpenMode
, aWriteParams
),
1129 mReadParams(aReadParams
)
1131 MOZ_ASSERT(IsMainProcess());
1132 MOZ_ASSERT(!NS_IsMainThread());
1133 MOZ_COUNT_CTOR(SingleProcessRunnable
);
1137 ~SingleProcessRunnable()
1139 MOZ_COUNT_DTOR(SingleProcessRunnable
);
1144 OnOpenMetadataForRead(const Metadata
& aMetadata
) MOZ_OVERRIDE
1146 uint32_t moduleIndex
;
1147 if (FindHashMatch(aMetadata
, mReadParams
, &moduleIndex
)) {
1148 MainProcessRunnable::OpenForRead(moduleIndex
);
1150 MainProcessRunnable::CacheMiss();
1155 OnOpenCacheFile() MOZ_OVERRIDE
1161 Close() MOZ_OVERRIDE MOZ_FINAL
1163 MainProcessRunnable::Close();
1167 OnFailure(JS::AsmJSCacheResult aResult
) MOZ_OVERRIDE
1169 MainProcessRunnable::OnFailure(aResult
);
1170 File::OnFailure(aResult
);
1174 OnClose() MOZ_OVERRIDE MOZ_FINAL
1176 MainProcessRunnable::OnClose();
1180 // Avoid MSVC 'dominance' warning by having clear Run() override.
1184 return MainProcessRunnable::Run();
1187 ReadParams mReadParams
;
1190 // A runnable that executes in a parent process for a cache access originating
1191 // in the content process. This runnable gets registered as an IPDL subprotocol
1192 // actor so that it can communicate with the corresponding ChildProcessRunnable.
1193 class ParentProcessRunnable MOZ_FINAL
: public PAsmJSCacheEntryParent
,
1194 public MainProcessRunnable
1197 // The given principal comes from an IPC::Principal which will be dec-refed
1198 // at the end of the message, so we must ref-count it here. Fortunately, we
1199 // are on the main thread (where PContent messages are delivered).
1200 ParentProcessRunnable(nsIPrincipal
* aPrincipal
,
1202 WriteParams aWriteParams
)
1203 : MainProcessRunnable(aPrincipal
, aOpenMode
, aWriteParams
),
1204 mPrincipalHolder(aPrincipal
),
1205 mActorDestroyed(false),
1209 MOZ_ASSERT(IsMainProcess());
1210 MOZ_ASSERT(NS_IsMainThread());
1211 MOZ_COUNT_CTOR(ParentProcessRunnable
);
1215 ~ParentProcessRunnable()
1217 MOZ_ASSERT(!mPrincipalHolder
, "Should have already been released");
1218 MOZ_ASSERT(mActorDestroyed
);
1219 MOZ_ASSERT(mFinished
);
1220 MOZ_COUNT_DTOR(ParentProcessRunnable
);
1224 Recv__delete__(const JS::AsmJSCacheResult
& aResult
) MOZ_OVERRIDE
1226 MOZ_ASSERT(!mFinished
);
1230 MainProcessRunnable::Close();
1232 MainProcessRunnable::Fail();
1239 ActorDestroy(ActorDestroyReason why
) MOZ_OVERRIDE
1241 MOZ_ASSERT(!mActorDestroyed
);
1242 mActorDestroyed
= true;
1244 // Assume ActorDestroy can happen at any time, so probe the current state to
1245 // determine what needs to happen.
1254 MainProcessRunnable::Close();
1256 MainProcessRunnable::Fail();
1261 OnOpenMetadataForRead(const Metadata
& aMetadata
) MOZ_OVERRIDE
1263 MOZ_ASSERT(NS_IsMainThread());
1265 if (!SendOnOpenMetadataForRead(aMetadata
)) {
1266 unused
<< Send__delete__(this, JS::AsmJSCache_InternalError
);
1271 RecvSelectCacheFileToRead(const uint32_t& aModuleIndex
) MOZ_OVERRIDE
1273 MainProcessRunnable::OpenForRead(aModuleIndex
);
1278 RecvCacheMiss() MOZ_OVERRIDE
1280 MainProcessRunnable::CacheMiss();
1285 OnOpenCacheFile() MOZ_OVERRIDE
1287 MOZ_ASSERT(NS_IsMainThread());
1289 MOZ_ASSERT(!mOpened
);
1292 FileDescriptor::PlatformHandleType handle
=
1293 FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc
));
1294 if (!SendOnOpenCacheFile(mFileSize
, FileDescriptor(handle
))) {
1295 unused
<< Send__delete__(this, JS::AsmJSCache_InternalError
);
1300 OnClose() MOZ_OVERRIDE MOZ_FINAL
1302 MOZ_ASSERT(NS_IsMainThread());
1303 MOZ_ASSERT(mOpened
);
1307 MainProcessRunnable::OnClose();
1309 MOZ_ASSERT(mActorDestroyed
);
1311 mPrincipalHolder
= nullptr;
1315 OnFailure(JS::AsmJSCacheResult aResult
) MOZ_OVERRIDE
1317 MOZ_ASSERT(NS_IsMainThread());
1318 MOZ_ASSERT(!mOpened
);
1322 MainProcessRunnable::OnFailure(aResult
);
1324 if (!mActorDestroyed
) {
1325 unused
<< Send__delete__(this, aResult
);
1328 mPrincipalHolder
= nullptr;
1331 nsCOMPtr
<nsIPrincipal
> mPrincipalHolder
;
1332 bool mActorDestroyed
;
1337 } // unnamed namespace
1339 PAsmJSCacheEntryParent
*
1340 AllocEntryParent(OpenMode aOpenMode
,
1341 WriteParams aWriteParams
,
1342 nsIPrincipal
* aPrincipal
)
1344 nsRefPtr
<ParentProcessRunnable
> runnable
=
1345 new ParentProcessRunnable(aPrincipal
, aOpenMode
, aWriteParams
);
1347 nsresult rv
= NS_DispatchToMainThread(runnable
);
1348 NS_ENSURE_SUCCESS(rv
, nullptr);
1350 // Transfer ownership to IPDL.
1351 return runnable
.forget().take();
1355 DeallocEntryParent(PAsmJSCacheEntryParent
* aActor
)
1357 // Transfer ownership back from IPDL.
1358 nsRefPtr
<ParentProcessRunnable
> op
=
1359 dont_AddRef(static_cast<ParentProcessRunnable
*>(aActor
));
1364 class ChildProcessRunnable MOZ_FINAL
: public File
,
1365 public PAsmJSCacheEntryChild
1370 // In the single-process case, the calling JS compilation thread holds the
1371 // nsIPrincipal alive indirectly (via the global object -> compartment ->
1372 // principal) so we don't have to ref-count it here. This is fortunate since
1373 // we are off the main thread and nsIPrincipals can only be ref-counted on
1375 ChildProcessRunnable(nsIPrincipal
* aPrincipal
,
1377 WriteParams aWriteParams
,
1378 ReadParams aReadParams
)
1379 : mPrincipal(aPrincipal
),
1380 mOpenMode(aOpenMode
),
1381 mWriteParams(aWriteParams
),
1382 mReadParams(aReadParams
),
1383 mActorDestroyed(false),
1386 MOZ_ASSERT(!IsMainProcess());
1387 MOZ_ASSERT(!NS_IsMainThread());
1388 MOZ_COUNT_CTOR(ChildProcessRunnable
);
1392 ~ChildProcessRunnable()
1394 MOZ_ASSERT(mState
== eFinished
);
1395 MOZ_ASSERT(mActorDestroyed
);
1396 MOZ_COUNT_DTOR(ChildProcessRunnable
);
1401 RecvOnOpenMetadataForRead(const Metadata
& aMetadata
) MOZ_OVERRIDE
1403 MOZ_ASSERT(NS_IsMainThread());
1404 MOZ_ASSERT(mState
== eOpening
);
1406 uint32_t moduleIndex
;
1407 if (FindHashMatch(aMetadata
, mReadParams
, &moduleIndex
)) {
1408 return SendSelectCacheFileToRead(moduleIndex
);
1411 return SendCacheMiss();
1415 RecvOnOpenCacheFile(const int64_t& aFileSize
,
1416 const FileDescriptor
& aFileDesc
) MOZ_OVERRIDE
1418 MOZ_ASSERT(NS_IsMainThread());
1419 MOZ_ASSERT(mState
== eOpening
);
1421 mFileSize
= aFileSize
;
1423 mFileDesc
= PR_ImportFile(PROsfd(aFileDesc
.PlatformHandle()));
1434 Recv__delete__(const JS::AsmJSCacheResult
& aResult
) MOZ_OVERRIDE
1436 MOZ_ASSERT(NS_IsMainThread());
1437 MOZ_ASSERT(mState
== eOpening
);
1444 ActorDestroy(ActorDestroyReason why
) MOZ_OVERRIDE
1446 MOZ_ASSERT(NS_IsMainThread());
1447 mActorDestroyed
= true;
1451 Close() MOZ_OVERRIDE MOZ_FINAL
1453 MOZ_ASSERT(mState
== eOpened
);
1456 NS_DispatchToMainThread(this);
1461 Fail(JS::AsmJSCacheResult aResult
)
1463 MOZ_ASSERT(NS_IsMainThread());
1464 MOZ_ASSERT(mState
== eInitial
|| mState
== eOpening
);
1467 File::OnFailure(aResult
);
1470 nsIPrincipal
* const mPrincipal
;
1471 const OpenMode mOpenMode
;
1472 WriteParams mWriteParams
;
1473 ReadParams mReadParams
;
1474 bool mActorDestroyed
;
1477 eInitial
, // Just created, waiting to dispatched to the main thread
1478 eOpening
, // Waiting for the parent process to respond
1479 eOpened
, // Parent process opened the entry and sent it back
1480 eClosing
, // Waiting to be dispatched to the main thread to Send__delete__
1481 eFinished
// Terminal state
1487 ChildProcessRunnable::Run()
1491 MOZ_ASSERT(NS_IsMainThread());
1493 // AddRef to keep this runnable alive until IPDL deallocates the
1494 // subprotocol (DeallocEntryChild).
1497 if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor(
1498 this, mOpenMode
, mWriteParams
, IPC::Principal(mPrincipal
)))
1500 // On failure, undo the AddRef (since DeallocEntryChild will not be
1501 // called) and unblock the parsing thread with a failure. The main
1502 // thread event loop still holds an outstanding ref which will keep
1503 // 'this' alive until returning to the event loop.
1506 Fail(JS::AsmJSCache_InternalError
);
1515 MOZ_ASSERT(NS_IsMainThread());
1517 // Per FileDescriptorHolder::Finish()'s comment, call before
1518 // AllowNextSynchronizedOp (which happens in the parent upon receipt of
1519 // the Send__delete__ message).
1522 if (!mActorDestroyed
) {
1523 unused
<< Send__delete__(this, JS::AsmJSCache_Success
);
1533 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
1537 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
1541 } // unnamed namespace
1544 DeallocEntryChild(PAsmJSCacheEntryChild
* aActor
)
1546 // Match the AddRef before SendPAsmJSCacheEntryConstructor.
1547 static_cast<ChildProcessRunnable
*>(aActor
)->Release();
1552 JS::AsmJSCacheResult
1553 OpenFile(nsIPrincipal
* aPrincipal
,
1555 WriteParams aWriteParams
,
1556 ReadParams aReadParams
,
1557 File::AutoClose
* aFile
)
1559 MOZ_ASSERT_IF(aOpenMode
== eOpenForRead
, aWriteParams
.mSize
== 0);
1560 MOZ_ASSERT_IF(aOpenMode
== eOpenForWrite
, aReadParams
.mBegin
== nullptr);
1562 // There are three reasons we don't attempt caching from the main thread:
1563 // 1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
1564 // synchronous waiting on the main thread requiring a runnable to be
1565 // dispatched to the main thread.
1566 // 2. In the child process: the IPDL PContent messages we need to
1567 // synchronously wait on are dispatched to the main thread.
1568 // 3. While a cache lookup *should* be much faster than compilation, IO
1569 // operations can be unpredictably slow and we'd like to avoid the
1570 // occasional janks on the main thread.
1571 // We could use a nested event loop to address 1 and 2, but we're potentially
1572 // in the middle of running JS (eval()) and nested event loops can be
1573 // semantically observable.
1574 if (NS_IsMainThread()) {
1575 return JS::AsmJSCache_SynchronousScript
;
1578 // If we are in a child process, we need to synchronously call into the
1579 // parent process to open the file and interact with the QuotaManager. The
1580 // child can then map the file into its address space to perform I/O.
1581 nsRefPtr
<File
> file
;
1582 if (IsMainProcess()) {
1583 file
= new SingleProcessRunnable(aPrincipal
, aOpenMode
, aWriteParams
,
1586 file
= new ChildProcessRunnable(aPrincipal
, aOpenMode
, aWriteParams
,
1590 JS::AsmJSCacheResult openResult
= file
->BlockUntilOpen(aFile
);
1591 if (openResult
!= JS::AsmJSCache_Success
) {
1595 if (!file
->MapMemory(aOpenMode
)) {
1596 return JS::AsmJSCache_InternalError
;
1599 return JS::AsmJSCache_Success
;
1602 } // anonymous namespace
1604 typedef uint32_t AsmJSCookieType
;
1605 static const uint32_t sAsmJSCookie
= 0x600d600d;
1608 OpenEntryForRead(nsIPrincipal
* aPrincipal
,
1609 const char16_t
* aBegin
,
1610 const char16_t
* aLimit
,
1612 const uint8_t** aMemory
,
1615 if (size_t(aLimit
- aBegin
) < sMinCachedModuleLength
) {
1619 ReadParams readParams
;
1620 readParams
.mBegin
= aBegin
;
1621 readParams
.mLimit
= aLimit
;
1623 File::AutoClose file
;
1624 WriteParams notAWrite
;
1625 JS::AsmJSCacheResult openResult
=
1626 OpenFile(aPrincipal
, eOpenForRead
, notAWrite
, readParams
, &file
);
1627 if (openResult
!= JS::AsmJSCache_Success
) {
1631 // Although we trust that the stored cache files have not been arbitrarily
1632 // corrupted, it is possible that a previous execution aborted in the middle
1633 // of writing a cache file (crash, OOM-killer, etc). To protect against
1634 // partially-written cache files, we use the following scheme:
1635 // - Allocate an extra word at the beginning of every cache file which
1636 // starts out 0 (OpenFile opens with PR_TRUNCATE).
1637 // - After the asm.js serialization is complete, PR_SyncMemMap to write
1638 // everything to disk and then store a non-zero value (sAsmJSCookie)
1639 // in the first word.
1640 // - When attempting to read a cache file, check whether the first word is
1642 if (file
->FileSize() < sizeof(AsmJSCookieType
) ||
1643 *(AsmJSCookieType
*)file
->MappedMemory() != sAsmJSCookie
) {
1647 *aSize
= file
->FileSize() - sizeof(AsmJSCookieType
);
1648 *aMemory
= (uint8_t*) file
->MappedMemory() + sizeof(AsmJSCookieType
);
1650 // The caller guarnatees a call to CloseEntryForRead (on success or
1651 // failure) at which point the file will be closed.
1652 file
.Forget(reinterpret_cast<File
**>(aFile
));
1657 CloseEntryForRead(size_t aSize
,
1658 const uint8_t* aMemory
,
1661 File::AutoClose
file(reinterpret_cast<File
*>(aFile
));
1663 MOZ_ASSERT(aSize
+ sizeof(AsmJSCookieType
) == file
->FileSize());
1664 MOZ_ASSERT(aMemory
- sizeof(AsmJSCookieType
) == file
->MappedMemory());
1667 JS::AsmJSCacheResult
1668 OpenEntryForWrite(nsIPrincipal
* aPrincipal
,
1670 const char16_t
* aBegin
,
1671 const char16_t
* aEnd
,
1676 if (size_t(aEnd
- aBegin
) < sMinCachedModuleLength
) {
1677 return JS::AsmJSCache_ModuleTooSmall
;
1680 // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
1681 aSize
+= sizeof(AsmJSCookieType
);
1683 static_assert(sNumFastHashChars
< sMinCachedModuleLength
, "HashString safe");
1685 WriteParams writeParams
;
1686 writeParams
.mInstalled
= aInstalled
;
1687 writeParams
.mSize
= aSize
;
1688 writeParams
.mFastHash
= HashString(aBegin
, sNumFastHashChars
);
1689 writeParams
.mNumChars
= aEnd
- aBegin
;
1690 writeParams
.mFullHash
= HashString(aBegin
, writeParams
.mNumChars
);
1692 File::AutoClose file
;
1693 ReadParams notARead
;
1694 JS::AsmJSCacheResult openResult
=
1695 OpenFile(aPrincipal
, eOpenForWrite
, writeParams
, notARead
, &file
);
1696 if (openResult
!= JS::AsmJSCache_Success
) {
1700 // Strip off the AsmJSCookieType from the buffer returned to the caller,
1701 // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
1703 *aMemory
= (uint8_t*) file
->MappedMemory() + sizeof(AsmJSCookieType
);
1705 // The caller guarnatees a call to CloseEntryForWrite (on success or
1706 // failure) at which point the file will be closed
1707 file
.Forget(reinterpret_cast<File
**>(aFile
));
1708 return JS::AsmJSCache_Success
;
1712 CloseEntryForWrite(size_t aSize
,
1716 File::AutoClose
file(reinterpret_cast<File
*>(aFile
));
1718 MOZ_ASSERT(aSize
+ sizeof(AsmJSCookieType
) == file
->FileSize());
1719 MOZ_ASSERT(aMemory
- sizeof(AsmJSCookieType
) == file
->MappedMemory());
1721 // Flush to disk before writing the cookie (see OpenEntryForRead).
1722 if (PR_SyncMemMap(file
->FileDesc(),
1723 file
->MappedMemory(),
1724 file
->FileSize()) == PR_SUCCESS
) {
1725 *(AsmJSCookieType
*)file
->MappedMemory() = sAsmJSCookie
;
1730 GetBuildId(JS::BuildIdCharVector
* aBuildID
)
1732 nsCOMPtr
<nsIXULAppInfo
> info
= do_GetService("@mozilla.org/xre/app-info;1");
1738 nsresult rv
= info
->GetPlatformBuildID(buildID
);
1739 NS_ENSURE_SUCCESS(rv
, false);
1741 if (!aBuildID
->resize(buildID
.Length())) {
1745 for (size_t i
= 0; i
< buildID
.Length(); i
++) {
1746 (*aBuildID
)[i
] = buildID
[i
];
1752 class Client
: public quota::Client
1757 NS_IMETHOD_(MozExternalRefCountType
)
1758 AddRef() MOZ_OVERRIDE
;
1760 NS_IMETHOD_(MozExternalRefCountType
)
1761 Release() MOZ_OVERRIDE
;
1764 GetType() MOZ_OVERRIDE
1770 InitOrigin(PersistenceType aPersistenceType
,
1771 const nsACString
& aGroup
,
1772 const nsACString
& aOrigin
,
1773 UsageInfo
* aUsageInfo
) MOZ_OVERRIDE
1778 return GetUsageForOrigin(aPersistenceType
, aGroup
, aOrigin
, aUsageInfo
);
1782 GetUsageForOrigin(PersistenceType aPersistenceType
,
1783 const nsACString
& aGroup
,
1784 const nsACString
& aOrigin
,
1785 UsageInfo
* aUsageInfo
) MOZ_OVERRIDE
1787 QuotaManager
* qm
= QuotaManager::Get();
1788 MOZ_ASSERT(qm
, "We were being called by the QuotaManager");
1790 nsCOMPtr
<nsIFile
> directory
;
1791 nsresult rv
= qm
->GetDirectoryForOrigin(aPersistenceType
, aOrigin
,
1792 getter_AddRefs(directory
));
1793 NS_ENSURE_SUCCESS(rv
, rv
);
1794 MOZ_ASSERT(directory
, "We're here because the origin directory exists");
1796 rv
= directory
->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME
));
1797 NS_ENSURE_SUCCESS(rv
, rv
);
1799 DebugOnly
<bool> exists
;
1800 MOZ_ASSERT(NS_SUCCEEDED(directory
->Exists(&exists
)) && exists
);
1802 nsCOMPtr
<nsISimpleEnumerator
> entries
;
1803 rv
= directory
->GetDirectoryEntries(getter_AddRefs(entries
));
1804 NS_ENSURE_SUCCESS(rv
, rv
);
1807 while (NS_SUCCEEDED((rv
= entries
->HasMoreElements(&hasMore
))) &&
1808 hasMore
&& !aUsageInfo
->Canceled()) {
1809 nsCOMPtr
<nsISupports
> entry
;
1810 rv
= entries
->GetNext(getter_AddRefs(entry
));
1811 NS_ENSURE_SUCCESS(rv
, rv
);
1813 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(entry
);
1814 NS_ENSURE_TRUE(file
, NS_NOINTERFACE
);
1817 rv
= file
->GetFileSize(&fileSize
);
1818 NS_ENSURE_SUCCESS(rv
, rv
);
1820 MOZ_ASSERT(fileSize
>= 0, "Negative size?!");
1822 // Since the client is not explicitly storing files, append to database
1823 // usage which represents implicit storage allocation.
1824 aUsageInfo
->AppendToDatabaseUsage(uint64_t(fileSize
));
1826 NS_ENSURE_SUCCESS(rv
, rv
);
1832 OnOriginClearCompleted(PersistenceType aPersistenceType
,
1833 const nsACString
& aOrigin
)
1838 ReleaseIOThreadObjects() MOZ_OVERRIDE
1842 IsFileServiceUtilized() MOZ_OVERRIDE
1848 IsTransactionServiceActivated() MOZ_OVERRIDE
1854 WaitForStoragesToComplete(nsTArray
<nsIOfflineStorage
*>& aStorages
,
1855 nsIRunnable
* aCallback
) MOZ_OVERRIDE
1857 MOZ_ASSERT_UNREACHABLE("There are no storages");
1861 ShutdownTransactionService() MOZ_OVERRIDE
1865 nsAutoRefCnt mRefCnt
;
1866 NS_DECL_OWNINGTHREAD
1869 NS_IMPL_ADDREF(asmjscache::Client
)
1870 NS_IMPL_RELEASE(asmjscache::Client
)
1875 return new Client();
1878 } // namespace asmjscache
1880 } // namespace mozilla
1884 using mozilla::dom::asmjscache::Metadata
;
1885 using mozilla::dom::asmjscache::WriteParams
;
1888 ParamTraits
<Metadata
>::Write(Message
* aMsg
, const paramType
& aParam
)
1890 for (unsigned i
= 0; i
< Metadata::kNumEntries
; i
++) {
1891 const Metadata::Entry
& entry
= aParam
.mEntries
[i
];
1892 WriteParam(aMsg
, entry
.mFastHash
);
1893 WriteParam(aMsg
, entry
.mNumChars
);
1894 WriteParam(aMsg
, entry
.mFullHash
);
1895 WriteParam(aMsg
, entry
.mModuleIndex
);
1900 ParamTraits
<Metadata
>::Read(const Message
* aMsg
, void** aIter
,
1903 for (unsigned i
= 0; i
< Metadata::kNumEntries
; i
++) {
1904 Metadata::Entry
& entry
= aResult
->mEntries
[i
];
1905 if (!ReadParam(aMsg
, aIter
, &entry
.mFastHash
) ||
1906 !ReadParam(aMsg
, aIter
, &entry
.mNumChars
) ||
1907 !ReadParam(aMsg
, aIter
, &entry
.mFullHash
) ||
1908 !ReadParam(aMsg
, aIter
, &entry
.mModuleIndex
))
1917 ParamTraits
<Metadata
>::Log(const paramType
& aParam
, std::wstring
* aLog
)
1919 for (unsigned i
= 0; i
< Metadata::kNumEntries
; i
++) {
1920 const Metadata::Entry
& entry
= aParam
.mEntries
[i
];
1921 LogParam(entry
.mFastHash
, aLog
);
1922 LogParam(entry
.mNumChars
, aLog
);
1923 LogParam(entry
.mFullHash
, aLog
);
1924 LogParam(entry
.mModuleIndex
, aLog
);
1929 ParamTraits
<WriteParams
>::Write(Message
* aMsg
, const paramType
& aParam
)
1931 WriteParam(aMsg
, aParam
.mSize
);
1932 WriteParam(aMsg
, aParam
.mFastHash
);
1933 WriteParam(aMsg
, aParam
.mNumChars
);
1934 WriteParam(aMsg
, aParam
.mFullHash
);
1935 WriteParam(aMsg
, aParam
.mInstalled
);
1939 ParamTraits
<WriteParams
>::Read(const Message
* aMsg
, void** aIter
,
1942 return ReadParam(aMsg
, aIter
, &aResult
->mSize
) &&
1943 ReadParam(aMsg
, aIter
, &aResult
->mFastHash
) &&
1944 ReadParam(aMsg
, aIter
, &aResult
->mNumChars
) &&
1945 ReadParam(aMsg
, aIter
, &aResult
->mFullHash
) &&
1946 ReadParam(aMsg
, aIter
, &aResult
->mInstalled
);
1950 ParamTraits
<WriteParams
>::Log(const paramType
& aParam
, std::wstring
* aLog
)
1952 LogParam(aParam
.mSize
, aLog
);
1953 LogParam(aParam
.mFastHash
, aLog
);
1954 LogParam(aParam
.mNumChars
, aLog
);
1955 LogParam(aParam
.mFullHash
, aLog
);
1956 LogParam(aParam
.mInstalled
, aLog
);