michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AsmJSCache.h" michael@0: michael@0: #include michael@0: michael@0: #include "js/RootingAPI.h" michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h" michael@0: #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/dom/PermissionMessageUtils.h" michael@0: #include "mozilla/dom/quota/Client.h" michael@0: #include "mozilla/dom/quota/OriginOrPatternString.h" michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "mozilla/dom/quota/QuotaObject.h" michael@0: #include "mozilla/dom/quota/UsageInfo.h" michael@0: #include "mozilla/HashFunctions.h" michael@0: #include "mozilla/unused.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIRunnable.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIThread.h" michael@0: #include "nsIXULAppInfo.h" michael@0: #include "nsJSPrincipals.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "prio.h" michael@0: #include "private/pprio.h" michael@0: michael@0: #define ASMJSCACHE_METADATA_FILE_NAME "metadata" michael@0: #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module" michael@0: michael@0: using mozilla::dom::quota::AssertIsOnIOThread; michael@0: using mozilla::dom::quota::OriginOrPatternString; michael@0: using mozilla::dom::quota::PersistenceType; michael@0: using mozilla::dom::quota::QuotaManager; michael@0: using mozilla::dom::quota::QuotaObject; michael@0: using mozilla::dom::quota::UsageInfo; michael@0: using mozilla::unused; michael@0: using mozilla::HashString; michael@0: michael@0: namespace mozilla { michael@0: michael@0: MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close); michael@0: michael@0: namespace dom { michael@0: namespace asmjscache { michael@0: michael@0: namespace { michael@0: michael@0: bool michael@0: IsMainProcess() michael@0: { michael@0: return XRE_GetProcessType() == GeckoProcessType_Default; michael@0: } michael@0: michael@0: // Anything smaller should compile fast enough that caching will just add michael@0: // overhead. michael@0: static const size_t sMinCachedModuleLength = 10000; michael@0: michael@0: // The number of characters to hash into the Metadata::Entry::mFastHash. michael@0: static const unsigned sNumFastHashChars = 4096; michael@0: michael@0: nsresult michael@0: WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) michael@0: { michael@0: int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE; michael@0: michael@0: JS::BuildIdCharVector buildId; michael@0: bool ok = GetBuildId(&buildId); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: ScopedPRFileDesc fd; michael@0: nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t length = buildId.length(); michael@0: int32_t bytesWritten = PR_Write(fd, &length, sizeof(length)); michael@0: NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED); michael@0: michael@0: bytesWritten = PR_Write(fd, buildId.begin(), length); michael@0: NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED); michael@0: michael@0: bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata)); michael@0: NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata) michael@0: { michael@0: int32_t openFlags = PR_RDONLY; michael@0: michael@0: ScopedPRFileDesc fd; michael@0: nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Read the buildid and check that it matches the current buildid michael@0: michael@0: JS::BuildIdCharVector currentBuildId; michael@0: bool ok = GetBuildId(¤tBuildId); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: uint32_t length; michael@0: int32_t bytesRead = PR_Read(fd, &length, sizeof(length)); michael@0: NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED); michael@0: michael@0: NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED); michael@0: michael@0: JS::BuildIdCharVector fileBuildId; michael@0: ok = fileBuildId.resize(length); michael@0: NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: bytesRead = PR_Read(fd, fileBuildId.begin(), length); michael@0: NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED); michael@0: michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: if (currentBuildId[i] != fileBuildId[i]) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // Read the Metadata struct michael@0: michael@0: bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata)); michael@0: NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile) michael@0: { michael@0: nsCOMPtr cacheFile; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE); michael@0: cacheFileName.AppendInt(aModuleIndex); michael@0: rv = cacheFile->Append(cacheFileName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: cacheFile.forget(aCacheFile); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class AutoDecreaseUsageForOrigin michael@0: { michael@0: const nsACString& mGroup; michael@0: const nsACString& mOrigin; michael@0: michael@0: public: michael@0: uint64_t mFreed; michael@0: michael@0: AutoDecreaseUsageForOrigin(const nsACString& aGroup, michael@0: const nsACString& aOrigin) michael@0: michael@0: : mGroup(aGroup), michael@0: mOrigin(aOrigin), michael@0: mFreed(0) michael@0: { } michael@0: michael@0: ~AutoDecreaseUsageForOrigin() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: if (!mFreed) { michael@0: return; michael@0: } michael@0: michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); michael@0: michael@0: qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY, michael@0: mGroup, mOrigin, mFreed); michael@0: } michael@0: }; michael@0: michael@0: static void michael@0: EvictEntries(nsIFile* aDirectory, const nsACString& aGroup, michael@0: const nsACString& aOrigin, uint64_t aNumBytes, michael@0: Metadata& aMetadata) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: AutoDecreaseUsageForOrigin usage(aGroup, aOrigin); michael@0: michael@0: for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) { michael@0: Metadata::Entry& entry = aMetadata.mEntries[i]; michael@0: unsigned moduleIndex = entry.mModuleIndex; michael@0: michael@0: nsCOMPtr file; michael@0: nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return; michael@0: } michael@0: michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return; michael@0: } michael@0: michael@0: if (exists) { michael@0: int64_t fileSize; michael@0: rv = file->GetFileSize(&fileSize); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return; michael@0: } michael@0: michael@0: rv = file->Remove(false); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return; michael@0: } michael@0: michael@0: usage.mFreed += fileSize; michael@0: } michael@0: michael@0: entry.clear(); michael@0: } michael@0: } michael@0: michael@0: // FileDescriptorHolder owns a file descriptor and its memory mapping. michael@0: // FileDescriptorHolder is derived by all three runnable classes (that is, michael@0: // (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds, michael@0: // FileDescriptorHolder is derived virtually by File and MainProcessRunnable for michael@0: // the benefit of SingleProcessRunnable, which derives both. Since File and michael@0: // MainProcessRunnable both need to be runnables, FileDescriptorHolder also michael@0: // derives nsRunnable. michael@0: class FileDescriptorHolder : public nsRunnable michael@0: { michael@0: public: michael@0: FileDescriptorHolder() michael@0: : mQuotaObject(nullptr), michael@0: mFileSize(INT64_MIN), michael@0: mFileDesc(nullptr), michael@0: mFileMap(nullptr), michael@0: mMappedMemory(nullptr) michael@0: { } michael@0: michael@0: ~FileDescriptorHolder() michael@0: { michael@0: // These resources should have already been released by Finish(). michael@0: MOZ_ASSERT(!mQuotaObject); michael@0: MOZ_ASSERT(!mMappedMemory); michael@0: MOZ_ASSERT(!mFileMap); michael@0: MOZ_ASSERT(!mFileDesc); michael@0: } michael@0: michael@0: size_t michael@0: FileSize() const michael@0: { michael@0: MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file"); michael@0: return mFileSize; michael@0: } michael@0: michael@0: PRFileDesc* michael@0: FileDesc() const michael@0: { michael@0: MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file"); michael@0: return mFileDesc; michael@0: } michael@0: michael@0: bool michael@0: MapMemory(OpenMode aOpenMode) michael@0: { michael@0: MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice"); michael@0: michael@0: PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY michael@0: : PR_PROT_READWRITE; michael@0: michael@0: mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags); michael@0: NS_ENSURE_TRUE(mFileMap, false); michael@0: michael@0: mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize); michael@0: NS_ENSURE_TRUE(mMappedMemory, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void* michael@0: MappedMemory() const michael@0: { michael@0: MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file"); michael@0: return mMappedMemory; michael@0: } michael@0: michael@0: protected: michael@0: // This method must be called before AllowNextSynchronizedOp (which releases michael@0: // the lock protecting these resources). It is idempotent, so it is ok to call michael@0: // multiple times (or before the file has been fully opened). michael@0: void michael@0: Finish() michael@0: { michael@0: if (mMappedMemory) { michael@0: PR_MemUnmap(mMappedMemory, mFileSize); michael@0: mMappedMemory = nullptr; michael@0: } michael@0: if (mFileMap) { michael@0: PR_CloseFileMap(mFileMap); michael@0: mFileMap = nullptr; michael@0: } michael@0: if (mFileDesc) { michael@0: PR_Close(mFileDesc); michael@0: mFileDesc = nullptr; michael@0: } michael@0: michael@0: // Holding the QuotaObject alive until all the cache files are closed enables michael@0: // assertions in QuotaManager that the cache entry isn't cleared while we michael@0: // are working on it. michael@0: mQuotaObject = nullptr; michael@0: } michael@0: michael@0: nsRefPtr mQuotaObject; michael@0: int64_t mFileSize; michael@0: PRFileDesc* mFileDesc; michael@0: PRFileMap* mFileMap; michael@0: void* mMappedMemory; michael@0: }; michael@0: michael@0: // File is a base class shared by (Single|Client)ProcessEntryRunnable that michael@0: // presents a single interface to the AsmJSCache ops which need to wait until michael@0: // the file is open, regardless of whether we are executing in the main process michael@0: // or not. michael@0: class File : public virtual FileDescriptorHolder michael@0: { michael@0: public: michael@0: class AutoClose michael@0: { michael@0: File* mFile; michael@0: michael@0: public: michael@0: explicit AutoClose(File* aFile = nullptr) michael@0: : mFile(aFile) michael@0: { } michael@0: michael@0: void michael@0: Init(File* aFile) michael@0: { michael@0: MOZ_ASSERT(!mFile); michael@0: mFile = aFile; michael@0: } michael@0: michael@0: File* michael@0: operator->() const michael@0: { michael@0: MOZ_ASSERT(mFile); michael@0: return mFile; michael@0: } michael@0: michael@0: void michael@0: Forget(File** aFile) michael@0: { michael@0: *aFile = mFile; michael@0: mFile = nullptr; michael@0: } michael@0: michael@0: ~AutoClose() michael@0: { michael@0: if (mFile) { michael@0: mFile->Close(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: bool michael@0: BlockUntilOpen(AutoClose* aCloser) michael@0: { michael@0: MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once"); michael@0: MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once"); michael@0: michael@0: mWaiting = true; michael@0: michael@0: nsresult rv = NS_DispatchToMainThread(this); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: while (mWaiting) { michael@0: mCondVar.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (!mOpened) { michael@0: return false; michael@0: } michael@0: michael@0: // Now that we're open, we're guarnateed a Close() call. However, we are michael@0: // not guarnateed someone is holding an outstanding reference until the File michael@0: // is closed, so we do that ourselves and Release() in OnClose(). michael@0: aCloser->Init(this); michael@0: AddRef(); michael@0: return true; michael@0: } michael@0: michael@0: // This method must be called if BlockUntilOpen returns 'true'. AutoClose michael@0: // mostly takes care of this. A derived class that implements Close() must michael@0: // guarnatee that OnClose() is called (eventually). michael@0: virtual void michael@0: Close() = 0; michael@0: michael@0: protected: michael@0: File() michael@0: : mMutex("File::mMutex"), michael@0: mCondVar(mMutex, "File::mCondVar"), michael@0: mWaiting(false), michael@0: mOpened(false) michael@0: { } michael@0: michael@0: ~File() michael@0: { michael@0: MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting"); michael@0: MOZ_ASSERT(!mOpened, "OnClose() should have been called"); michael@0: } michael@0: michael@0: void michael@0: OnOpen() michael@0: { michael@0: Notify(true); michael@0: } michael@0: michael@0: void michael@0: OnFailure() michael@0: { michael@0: FileDescriptorHolder::Finish(); michael@0: michael@0: Notify(false); michael@0: } michael@0: michael@0: void michael@0: OnClose() michael@0: { michael@0: FileDescriptorHolder::Finish(); michael@0: michael@0: MOZ_ASSERT(mOpened); michael@0: mOpened = false; michael@0: michael@0: // Match the AddRef in BlockUntilOpen(). The main thread event loop still michael@0: // holds an outstanding ref which will keep 'this' alive until returning to michael@0: // the event loop. michael@0: Release(); michael@0: } michael@0: michael@0: private: michael@0: void michael@0: Notify(bool aSuccess) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: MOZ_ASSERT(mWaiting); michael@0: michael@0: mWaiting = false; michael@0: mOpened = aSuccess; michael@0: mCondVar.Notify(); michael@0: } michael@0: michael@0: Mutex mMutex; michael@0: CondVar mCondVar; michael@0: bool mWaiting; michael@0: bool mOpened; michael@0: }; michael@0: michael@0: // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable michael@0: // that factors out the runnable state machine required to open a cache entry michael@0: // that runs in the main process. michael@0: class MainProcessRunnable : public virtual FileDescriptorHolder michael@0: { michael@0: public: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: // MainProcessRunnable runnable assumes that the derived class ensures michael@0: // (through ref-counting or preconditions) that aPrincipal is kept alive for michael@0: // the lifetime of the MainProcessRunnable. michael@0: MainProcessRunnable(nsIPrincipal* aPrincipal, michael@0: OpenMode aOpenMode, michael@0: WriteParams aWriteParams) michael@0: : mPrincipal(aPrincipal), michael@0: mOpenMode(aOpenMode), michael@0: mWriteParams(aWriteParams), michael@0: mNeedAllowNextSynchronizedOp(false), michael@0: mPersistence(quota::PERSISTENCE_TYPE_INVALID), michael@0: mState(eInitial) michael@0: { michael@0: MOZ_ASSERT(IsMainProcess()); michael@0: } michael@0: michael@0: virtual ~MainProcessRunnable() michael@0: { michael@0: MOZ_ASSERT(mState == eFinished); michael@0: MOZ_ASSERT(!mNeedAllowNextSynchronizedOp); michael@0: } michael@0: michael@0: protected: michael@0: // This method is called by the derived class on the main thread when a michael@0: // cache entry has been selected to open. michael@0: void michael@0: OpenForRead(unsigned aModuleIndex) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead); michael@0: MOZ_ASSERT(mOpenMode == eOpenForRead); michael@0: michael@0: mModuleIndex = aModuleIndex; michael@0: mState = eReadyToOpenCacheFileForRead; michael@0: DispatchToIOThread(); michael@0: } michael@0: michael@0: // This method is called by the derived class on the main thread when no cache michael@0: // entry was found to open. If we just tried a lookup in persistent storage michael@0: // then we might still get a hit in temporary storage (for an asm.js module michael@0: // that wasn't compiled at install-time). michael@0: void michael@0: CacheMiss() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eFailedToReadMetadata || michael@0: mState == eWaitingToOpenCacheFileForRead); michael@0: MOZ_ASSERT(mOpenMode == eOpenForRead); michael@0: michael@0: if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) { michael@0: Fail(); michael@0: return; michael@0: } michael@0: michael@0: // Try again with a clean slate. InitOnMainThread will see that mPersistence michael@0: // is initialized and switch to temporary storage. michael@0: MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT); michael@0: FinishOnMainThread(); michael@0: mState = eInitial; michael@0: NS_DispatchToMainThread(this); michael@0: } michael@0: michael@0: // This method is called by the derived class (either on the JS compilation michael@0: // thread or the main thread) when the JS engine is finished reading/writing michael@0: // the cache entry. michael@0: void michael@0: Close() michael@0: { michael@0: MOZ_ASSERT(mState == eOpened); michael@0: mState = eClosing; michael@0: NS_DispatchToMainThread(this); michael@0: } michael@0: michael@0: // This method is called both internally and by derived classes upon any michael@0: // failure that prevents the eventual opening of the cache entry. michael@0: void michael@0: Fail() michael@0: { michael@0: MOZ_ASSERT(mState != eOpened && michael@0: mState != eClosing && michael@0: mState != eFailing && michael@0: mState != eFinished); michael@0: michael@0: mState = eFailing; michael@0: NS_DispatchToMainThread(this); michael@0: } michael@0: michael@0: // Called by MainProcessRunnable on the main thread after metadata is open: michael@0: virtual void michael@0: OnOpenMetadataForRead(const Metadata& aMetadata) = 0; michael@0: michael@0: // Called by MainProcessRunnable on the main thread after the entry is open: michael@0: virtual void michael@0: OnOpenCacheFile() = 0; michael@0: michael@0: // This method may be overridden, but it must be called from the overrider. michael@0: // Called by MainProcessRunnable on the main thread after a call to Fail(): michael@0: virtual void michael@0: OnFailure() michael@0: { michael@0: FinishOnMainThread(); michael@0: } michael@0: michael@0: // This method may be overridden, but it must be called from the overrider. michael@0: // Called by MainProcessRunnable on the main thread after a call to Close(): michael@0: virtual void michael@0: OnClose() michael@0: { michael@0: FinishOnMainThread(); michael@0: } michael@0: michael@0: private: michael@0: nsresult michael@0: InitOnMainThread(); michael@0: michael@0: nsresult michael@0: ReadMetadata(); michael@0: michael@0: nsresult michael@0: OpenCacheFileForWrite(); michael@0: michael@0: nsresult michael@0: OpenCacheFileForRead(); michael@0: michael@0: void michael@0: FinishOnMainThread(); michael@0: michael@0: void michael@0: DispatchToIOThread() michael@0: { michael@0: // If shutdown just started, the QuotaManager may have been deleted. michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: if (!qm) { michael@0: Fail(); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsIPrincipal* const mPrincipal; michael@0: const OpenMode mOpenMode; michael@0: const WriteParams mWriteParams; michael@0: michael@0: // State initialized during eInitial: michael@0: bool mNeedAllowNextSynchronizedOp; michael@0: quota::PersistenceType mPersistence; michael@0: nsCString mGroup; michael@0: nsCString mOrigin; michael@0: nsCString mStorageId; michael@0: michael@0: // State initialized during eReadyToReadMetadata michael@0: nsCOMPtr mDirectory; michael@0: nsCOMPtr mMetadataFile; michael@0: Metadata mMetadata; michael@0: michael@0: // State initialized during eWaitingToOpenCacheFileForRead michael@0: unsigned mModuleIndex; michael@0: michael@0: enum State { michael@0: eInitial, // Just created, waiting to be dispatched to main thread michael@0: eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed michael@0: eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread michael@0: eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail michael@0: eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead michael@0: eWaitingToOpenCacheFileForRead, // Waiting to hear back from child michael@0: eReadyToOpenCacheFileForRead, // Waiting to open cache file for read michael@0: eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread michael@0: eOpened, // Finished calling OnOpen, waiting to be closed michael@0: eClosing, // Waiting to be dispatched to main thread again michael@0: eFailing, // Just failed, waiting to be dispatched to the main thread michael@0: eFinished, // Terminal state michael@0: }; michael@0: State mState; michael@0: }; michael@0: michael@0: nsresult michael@0: MainProcessRunnable::InitOnMainThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eInitial); michael@0: michael@0: QuotaManager* qm = QuotaManager::GetOrCreate(); michael@0: NS_ENSURE_STATE(qm); michael@0: michael@0: nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup, michael@0: &mOrigin, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isApp = mPrincipal->GetAppStatus() != michael@0: nsIPrincipal::APP_STATUS_NOT_INSTALLED; michael@0: michael@0: if (mOpenMode == eOpenForWrite) { michael@0: MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID); michael@0: if (mWriteParams.mInstalled) { michael@0: // If we are performing install-time caching of an app, we'd like to store michael@0: // the cache entry in persistent storage so the entry is never evicted, michael@0: // but we need to verify that the app has unlimited storage permissions michael@0: // first. Unlimited storage permissions justify us in skipping all quota michael@0: // checks when storing the cache entry and avoids all the issues around michael@0: // the persistent quota prompt. michael@0: MOZ_ASSERT(isApp); michael@0: michael@0: nsCOMPtr pm = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint32_t permission; michael@0: rv = pm->TestPermissionFromPrincipal(mPrincipal, michael@0: PERMISSION_STORAGE_UNLIMITED, michael@0: &permission); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); michael@0: michael@0: // If app doens't have the unlimited storage permission, we can still michael@0: // cache in temporary for a likely good first-run experience. michael@0: mPersistence = permission == nsIPermissionManager::ALLOW_ACTION michael@0: ? quota::PERSISTENCE_TYPE_PERSISTENT michael@0: : quota::PERSISTENCE_TYPE_TEMPORARY; michael@0: } else { michael@0: mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY; michael@0: } michael@0: } else { michael@0: // For the reasons described above, apps may have cache entries in both michael@0: // persistent and temporary storage. At lookup time we don't know how and michael@0: // where the given script was cached, so start the search in persistent michael@0: // storage and, if that fails, search in temporary storage. (Non-apps can michael@0: // only be stored in temporary storage.) michael@0: if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) { michael@0: mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT michael@0: : quota::PERSISTENCE_TYPE_TEMPORARY; michael@0: } else { michael@0: MOZ_ASSERT(isApp); michael@0: MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT); michael@0: mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY; michael@0: } michael@0: } michael@0: michael@0: QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS, michael@0: NS_LITERAL_STRING("asmjs"), mStorageId); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MainProcessRunnable::ReadMetadata() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: MOZ_ASSERT(mState == eReadyToReadMetadata); michael@0: michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); michael@0: michael@0: // Only track quota for temporary storage. For persistent storage, we've michael@0: // already checked that we have unlimited-storage permissions. michael@0: bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY; michael@0: michael@0: nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin, michael@0: trackQuota, michael@0: getter_AddRefs(mDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool exists; michael@0: rv = mDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: DebugOnly isDirectory; michael@0: MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory))); michael@0: MOZ_ASSERT(isDirectory, "Should have caught this earlier!"); michael@0: } michael@0: michael@0: rv = mDirectory->Clone(getter_AddRefs(mMetadataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMetadataFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) { michael@0: exists = false; michael@0: } michael@0: michael@0: if (!exists) { michael@0: // If we are reading, we can't possibly have a cache hit. michael@0: if (mOpenMode == eOpenForRead) { michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: // Initialize Metadata with a valid empty state for the LRU cache. michael@0: for (unsigned i = 0; i < Metadata::kNumEntries; i++) { michael@0: Metadata::Entry& entry = mMetadata.mEntries[i]; michael@0: entry.mModuleIndex = i; michael@0: entry.clear(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MainProcessRunnable::OpenCacheFileForWrite() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: MOZ_ASSERT(mState == eReadyToReadMetadata); michael@0: MOZ_ASSERT(mOpenMode == eOpenForWrite); michael@0: michael@0: mFileSize = mWriteParams.mSize; michael@0: michael@0: // Kick out the oldest entry in the LRU queue in the metadata. michael@0: mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex; michael@0: michael@0: nsCOMPtr file; michael@0: nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); michael@0: michael@0: // If we are allocating in temporary storage, ask the QuotaManager if we're michael@0: // within the quota. If we are allocating in persistent storage, we've already michael@0: // checked that we have the unlimited-storage permission, so there is nothing michael@0: // to check. michael@0: if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) { michael@0: // Create the QuotaObject before all file IO and keep it alive until caching michael@0: // completes to get maximum assertion coverage in QuotaManager against michael@0: // concurrent removal, etc. michael@0: mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file); michael@0: NS_ENSURE_STATE(mQuotaObject); michael@0: michael@0: if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) { michael@0: // If the request fails, it might be because mOrigin is using too much michael@0: // space (MaybeAllocateMoreSpace will not evict our own origin since it is michael@0: // active). Try to make some space by evicting LRU entries until there is michael@0: // enough space. michael@0: EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata); michael@0: if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE; michael@0: rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Move the mModuleIndex's LRU entry to the recent end of the queue. michael@0: PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry); michael@0: Metadata::Entry& entry = mMetadata.mEntries[0]; michael@0: entry.mFastHash = mWriteParams.mFastHash; michael@0: entry.mNumChars = mWriteParams.mNumChars; michael@0: entry.mFullHash = mWriteParams.mFullHash; michael@0: entry.mModuleIndex = mModuleIndex; michael@0: michael@0: rv = WriteMetadataFile(mMetadataFile, mMetadata); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: MainProcessRunnable::OpenCacheFileForRead() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead); michael@0: MOZ_ASSERT(mOpenMode == eOpenForRead); michael@0: michael@0: nsCOMPtr file; michael@0: nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread"); michael@0: michael@0: if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) { michael@0: // Even though it's not strictly necessary, create the QuotaObject before michael@0: // all file IO and keep it alive until caching completes to get maximum michael@0: // assertion coverage in QuotaManager against concurrent removal, etc. michael@0: mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file); michael@0: NS_ENSURE_STATE(mQuotaObject); michael@0: } michael@0: michael@0: rv = file->GetFileSize(&mFileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD; michael@0: rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Move the mModuleIndex's LRU entry to the recent end of the queue. michael@0: unsigned lruIndex = 0; michael@0: while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) { michael@0: if (++lruIndex == Metadata::kNumEntries) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: Metadata::Entry entry = mMetadata.mEntries[lruIndex]; michael@0: PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex); michael@0: mMetadata.mEntries[0] = entry; michael@0: michael@0: rv = WriteMetadataFile(mMetadataFile, mMetadata); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: MainProcessRunnable::FinishOnMainThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Per FileDescriptorHolder::Finish()'s comment, call before michael@0: // AllowNextSynchronizedOp. michael@0: FileDescriptorHolder::Finish(); michael@0: michael@0: if (mNeedAllowNextSynchronizedOp) { michael@0: mNeedAllowNextSynchronizedOp = false; michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: if (qm) { michael@0: qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin), michael@0: Nullable(mPersistence), michael@0: mStorageId); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: MainProcessRunnable::Run() michael@0: { michael@0: nsresult rv; michael@0: michael@0: // All success/failure paths must eventually call Finish() to avoid leaving michael@0: // the parser hanging. michael@0: switch (mState) { michael@0: case eInitial: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: rv = InitOnMainThread(); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mState = eWaitingToOpenMetadata; michael@0: rv = QuotaManager::Get()->WaitForOpenAllowed( michael@0: OriginOrPatternString::FromOrigin(mOrigin), michael@0: Nullable(mPersistence), michael@0: mStorageId, this); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mNeedAllowNextSynchronizedOp = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eWaitingToOpenMetadata: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mState = eReadyToReadMetadata; michael@0: DispatchToIOThread(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eReadyToReadMetadata: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: rv = ReadMetadata(); michael@0: if (NS_FAILED(rv)) { michael@0: mState = eFailedToReadMetadata; michael@0: NS_DispatchToMainThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mOpenMode == eOpenForRead) { michael@0: mState = eSendingMetadataForRead; michael@0: NS_DispatchToMainThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = OpenCacheFileForWrite(); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mState = eSendingCacheFile; michael@0: NS_DispatchToMainThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eFailedToReadMetadata: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: CacheMiss(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eSendingMetadataForRead: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mOpenMode == eOpenForRead); michael@0: michael@0: mState = eWaitingToOpenCacheFileForRead; michael@0: OnOpenMetadataForRead(mMetadata); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eReadyToOpenCacheFileForRead: { michael@0: AssertIsOnIOThread(); michael@0: MOZ_ASSERT(mOpenMode == eOpenForRead); michael@0: michael@0: rv = OpenCacheFileForRead(); michael@0: if (NS_FAILED(rv)) { michael@0: Fail(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mState = eSendingCacheFile; michael@0: NS_DispatchToMainThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eSendingCacheFile: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mState = eOpened; michael@0: OnOpenCacheFile(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eFailing: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mState = eFinished; michael@0: OnFailure(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eClosing: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mState = eFinished; michael@0: OnClose(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eWaitingToOpenCacheFileForRead: michael@0: case eOpened: michael@0: case eFinished: { michael@0: MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state"); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Corrupt state"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams, michael@0: unsigned* aModuleIndex) michael@0: { michael@0: // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry michael@0: // also stores an mFastHash of its first sNumFastHashChars so this gives us a michael@0: // fast way to probabilistically determine whether we have a cache hit. We michael@0: // still do a full hash of all the chars before returning the cache file to michael@0: // the engine to avoid penalizing the case where there are multiple cached michael@0: // asm.js modules where the first sNumFastHashChars are the same. The michael@0: // mFullHash of each cache entry can have a different mNumChars so the fast michael@0: // hash allows us to avoid performing up to Metadata::kNumEntries separate michael@0: // full hashes. michael@0: uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin; michael@0: MOZ_ASSERT(numChars > sNumFastHashChars); michael@0: uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars); michael@0: michael@0: for (unsigned i = 0; i < Metadata::kNumEntries ; i++) { michael@0: // Compare the "fast hash" first to see whether it is worthwhile to michael@0: // hash all the chars. michael@0: Metadata::Entry entry = aMetadata.mEntries[i]; michael@0: if (entry.mFastHash != fastHash) { michael@0: continue; michael@0: } michael@0: michael@0: // Assuming we have enough characters, hash all the chars it would take michael@0: // to match this cache entry and compare to the cache entry. If we get a michael@0: // hit we'll still do a full source match later (in the JS engine), but michael@0: // the full hash match means this is probably the cache entry we want. michael@0: if (numChars < entry.mNumChars) { michael@0: continue; michael@0: } michael@0: uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars); michael@0: if (entry.mFullHash != fullHash) { michael@0: continue; michael@0: } michael@0: michael@0: *aModuleIndex = entry.mModuleIndex; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // A runnable that executes for a cache access originating in the main process. michael@0: class SingleProcessRunnable MOZ_FINAL : public File, michael@0: private MainProcessRunnable michael@0: { michael@0: public: michael@0: // In the single-process case, the calling JS compilation thread holds the michael@0: // nsIPrincipal alive indirectly (via the global object -> compartment -> michael@0: // principal) so we don't have to ref-count it here. This is fortunate since michael@0: // we are off the main thread and nsIPrincipals can only be ref-counted on michael@0: // the main thread. michael@0: SingleProcessRunnable(nsIPrincipal* aPrincipal, michael@0: OpenMode aOpenMode, michael@0: WriteParams aWriteParams, michael@0: ReadParams aReadParams) michael@0: : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams), michael@0: mReadParams(aReadParams) michael@0: { michael@0: MOZ_ASSERT(IsMainProcess()); michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_COUNT_CTOR(SingleProcessRunnable); michael@0: } michael@0: michael@0: ~SingleProcessRunnable() michael@0: { michael@0: MOZ_COUNT_DTOR(SingleProcessRunnable); michael@0: } michael@0: michael@0: private: michael@0: void michael@0: OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE michael@0: { michael@0: uint32_t moduleIndex; michael@0: if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) { michael@0: MainProcessRunnable::OpenForRead(moduleIndex); michael@0: } else { michael@0: MainProcessRunnable::CacheMiss(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OnOpenCacheFile() MOZ_OVERRIDE michael@0: { michael@0: File::OnOpen(); michael@0: } michael@0: michael@0: void michael@0: Close() MOZ_OVERRIDE MOZ_FINAL michael@0: { michael@0: MainProcessRunnable::Close(); michael@0: } michael@0: michael@0: void michael@0: OnFailure() MOZ_OVERRIDE michael@0: { michael@0: MainProcessRunnable::OnFailure(); michael@0: File::OnFailure(); michael@0: } michael@0: michael@0: void michael@0: OnClose() MOZ_OVERRIDE MOZ_FINAL michael@0: { michael@0: MainProcessRunnable::OnClose(); michael@0: File::OnClose(); michael@0: } michael@0: michael@0: // Avoid MSVC 'dominance' warning by having clear Run() override. michael@0: NS_IMETHODIMP michael@0: Run() MOZ_OVERRIDE michael@0: { michael@0: return MainProcessRunnable::Run(); michael@0: } michael@0: michael@0: ReadParams mReadParams; michael@0: }; michael@0: michael@0: // A runnable that executes in a parent process for a cache access originating michael@0: // in the content process. This runnable gets registered as an IPDL subprotocol michael@0: // actor so that it can communicate with the corresponding ChildProcessRunnable. michael@0: class ParentProcessRunnable MOZ_FINAL : public PAsmJSCacheEntryParent, michael@0: public MainProcessRunnable michael@0: { michael@0: public: michael@0: // The given principal comes from an IPC::Principal which will be dec-refed michael@0: // at the end of the message, so we must ref-count it here. Fortunately, we michael@0: // are on the main thread (where PContent messages are delivered). michael@0: ParentProcessRunnable(nsIPrincipal* aPrincipal, michael@0: OpenMode aOpenMode, michael@0: WriteParams aWriteParams) michael@0: : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams), michael@0: mPrincipalHolder(aPrincipal), michael@0: mActorDestroyed(false), michael@0: mOpened(false), michael@0: mFinished(false) michael@0: { michael@0: MOZ_ASSERT(IsMainProcess()); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_COUNT_CTOR(ParentProcessRunnable); michael@0: } michael@0: michael@0: private: michael@0: ~ParentProcessRunnable() michael@0: { michael@0: MOZ_ASSERT(!mPrincipalHolder, "Should have already been released"); michael@0: MOZ_ASSERT(mActorDestroyed); michael@0: MOZ_ASSERT(mFinished); michael@0: MOZ_COUNT_DTOR(ParentProcessRunnable); michael@0: } michael@0: michael@0: bool michael@0: Recv__delete__() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!mFinished); michael@0: mFinished = true; michael@0: michael@0: if (mOpened) { michael@0: MainProcessRunnable::Close(); michael@0: } else { michael@0: MainProcessRunnable::Fail(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(!mActorDestroyed); michael@0: mActorDestroyed = true; michael@0: michael@0: // Assume ActorDestroy can happen at any time, so probe the current state to michael@0: // determine what needs to happen. michael@0: michael@0: if (mFinished) { michael@0: return; michael@0: } michael@0: michael@0: mFinished = true; michael@0: michael@0: if (mOpened) { michael@0: MainProcessRunnable::Close(); michael@0: } else { michael@0: MainProcessRunnable::Fail(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!SendOnOpenMetadataForRead(aMetadata)) { michael@0: unused << Send__delete__(this); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE michael@0: { michael@0: MainProcessRunnable::OpenForRead(aModuleIndex); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: RecvCacheMiss() MOZ_OVERRIDE michael@0: { michael@0: MainProcessRunnable::CacheMiss(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: OnOpenCacheFile() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: MOZ_ASSERT(!mOpened); michael@0: mOpened = true; michael@0: michael@0: FileDescriptor::PlatformHandleType handle = michael@0: FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc)); michael@0: if (!SendOnOpenCacheFile(mFileSize, handle)) { michael@0: unused << Send__delete__(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OnClose() MOZ_OVERRIDE MOZ_FINAL michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mOpened); michael@0: michael@0: mFinished = true; michael@0: michael@0: MainProcessRunnable::OnClose(); michael@0: michael@0: MOZ_ASSERT(mActorDestroyed); michael@0: michael@0: mPrincipalHolder = nullptr; michael@0: } michael@0: michael@0: void michael@0: OnFailure() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mOpened); michael@0: michael@0: mFinished = true; michael@0: michael@0: MainProcessRunnable::OnFailure(); michael@0: michael@0: if (!mActorDestroyed) { michael@0: unused << Send__delete__(this); michael@0: } michael@0: michael@0: mPrincipalHolder = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr mPrincipalHolder; michael@0: bool mActorDestroyed; michael@0: bool mOpened; michael@0: bool mFinished; michael@0: }; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: PAsmJSCacheEntryParent* michael@0: AllocEntryParent(OpenMode aOpenMode, michael@0: WriteParams aWriteParams, michael@0: nsIPrincipal* aPrincipal) michael@0: { michael@0: ParentProcessRunnable* runnable = michael@0: new ParentProcessRunnable(aPrincipal, aOpenMode, aWriteParams); michael@0: michael@0: // AddRef to keep the runnable alive until DeallocEntryParent. michael@0: runnable->AddRef(); michael@0: michael@0: nsresult rv = NS_DispatchToMainThread(runnable); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return runnable; michael@0: } michael@0: michael@0: void michael@0: DeallocEntryParent(PAsmJSCacheEntryParent* aActor) michael@0: { michael@0: // Match the AddRef in AllocEntryParent. michael@0: static_cast(aActor)->Release(); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class ChildProcessRunnable MOZ_FINAL : public File, michael@0: public PAsmJSCacheEntryChild michael@0: { michael@0: public: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: // In the single-process case, the calling JS compilation thread holds the michael@0: // nsIPrincipal alive indirectly (via the global object -> compartment -> michael@0: // principal) so we don't have to ref-count it here. This is fortunate since michael@0: // we are off the main thread and nsIPrincipals can only be ref-counted on michael@0: // the main thread. michael@0: ChildProcessRunnable(nsIPrincipal* aPrincipal, michael@0: OpenMode aOpenMode, michael@0: WriteParams aWriteParams, michael@0: ReadParams aReadParams) michael@0: : mPrincipal(aPrincipal), michael@0: mOpenMode(aOpenMode), michael@0: mWriteParams(aWriteParams), michael@0: mReadParams(aReadParams), michael@0: mActorDestroyed(false), michael@0: mState(eInitial) michael@0: { michael@0: MOZ_ASSERT(!IsMainProcess()); michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_COUNT_CTOR(ChildProcessRunnable); michael@0: } michael@0: michael@0: ~ChildProcessRunnable() michael@0: { michael@0: MOZ_ASSERT(mState == eFinished); michael@0: MOZ_ASSERT(mActorDestroyed); michael@0: MOZ_COUNT_DTOR(ChildProcessRunnable); michael@0: } michael@0: michael@0: private: michael@0: bool michael@0: RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eOpening); michael@0: michael@0: uint32_t moduleIndex; michael@0: if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) { michael@0: return SendSelectCacheFileToRead(moduleIndex); michael@0: } michael@0: michael@0: return SendCacheMiss(); michael@0: } michael@0: michael@0: bool michael@0: RecvOnOpenCacheFile(const int64_t& aFileSize, michael@0: const FileDescriptor& aFileDesc) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eOpening); michael@0: michael@0: mFileSize = aFileSize; michael@0: michael@0: mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle())); michael@0: if (!mFileDesc) { michael@0: return false; michael@0: } michael@0: michael@0: mState = eOpened; michael@0: File::OnOpen(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Recv__delete__() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eOpening); michael@0: michael@0: Fail(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mActorDestroyed = true; michael@0: } michael@0: michael@0: void michael@0: Close() MOZ_OVERRIDE MOZ_FINAL michael@0: { michael@0: MOZ_ASSERT(mState == eOpened); michael@0: michael@0: mState = eClosing; michael@0: NS_DispatchToMainThread(this); michael@0: } michael@0: michael@0: private: michael@0: void michael@0: Fail() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(mState == eInitial || mState == eOpening); michael@0: michael@0: mState = eFinished; michael@0: File::OnFailure(); michael@0: } michael@0: michael@0: nsIPrincipal* const mPrincipal; michael@0: const OpenMode mOpenMode; michael@0: WriteParams mWriteParams; michael@0: ReadParams mReadParams; michael@0: bool mActorDestroyed; michael@0: michael@0: enum State { michael@0: eInitial, // Just created, waiting to dispatched to the main thread michael@0: eOpening, // Waiting for the parent process to respond michael@0: eOpened, // Parent process opened the entry and sent it back michael@0: eClosing, // Waiting to be dispatched to the main thread to Send__delete__ michael@0: eFinished // Terminal state michael@0: }; michael@0: State mState; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: ChildProcessRunnable::Run() michael@0: { michael@0: switch (mState) { michael@0: case eInitial: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // AddRef to keep this runnable alive until IPDL deallocates the michael@0: // subprotocol (DeallocEntryChild). michael@0: AddRef(); michael@0: michael@0: if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor( michael@0: this, mOpenMode, mWriteParams, IPC::Principal(mPrincipal))) michael@0: { michael@0: // On failure, undo the AddRef (since DeallocEntryChild will not be michael@0: // called) and unblock the parsing thread with a failure. The main michael@0: // thread event loop still holds an outstanding ref which will keep michael@0: // 'this' alive until returning to the event loop. michael@0: Release(); michael@0: michael@0: Fail(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mState = eOpening; michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eClosing: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Per FileDescriptorHolder::Finish()'s comment, call before michael@0: // AllowNextSynchronizedOp (which happens in the parent upon receipt of michael@0: // the Send__delete__ message). michael@0: File::OnClose(); michael@0: michael@0: if (!mActorDestroyed) { michael@0: unused << Send__delete__(this); michael@0: } michael@0: michael@0: mState = eFinished; michael@0: return NS_OK; michael@0: } michael@0: michael@0: case eOpening: michael@0: case eOpened: michael@0: case eFinished: { michael@0: MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state"); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Corrupt state"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // unnamed namespace michael@0: michael@0: void michael@0: DeallocEntryChild(PAsmJSCacheEntryChild* aActor) michael@0: { michael@0: // Match the AddRef before SendPAsmJSCacheEntryConstructor. michael@0: static_cast(aActor)->Release(); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: bool michael@0: OpenFile(nsIPrincipal* aPrincipal, michael@0: OpenMode aOpenMode, michael@0: WriteParams aWriteParams, michael@0: ReadParams aReadParams, michael@0: File::AutoClose* aFile) michael@0: { michael@0: MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0); michael@0: MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr); michael@0: michael@0: // There are three reasons we don't attempt caching from the main thread: michael@0: // 1. In the parent process: QuotaManager::WaitForOpenAllowed prevents michael@0: // synchronous waiting on the main thread requiring a runnable to be michael@0: // dispatched to the main thread. michael@0: // 2. In the child process: the IPDL PContent messages we need to michael@0: // synchronously wait on are dispatched to the main thread. michael@0: // 3. While a cache lookup *should* be much faster than compilation, IO michael@0: // operations can be unpredictably slow and we'd like to avoid the michael@0: // occasional janks on the main thread. michael@0: // We could use a nested event loop to address 1 and 2, but we're potentially michael@0: // in the middle of running JS (eval()) and nested event loops can be michael@0: // semantically observable. michael@0: if (NS_IsMainThread()) { michael@0: return false; michael@0: } michael@0: michael@0: // If we are in a child process, we need to synchronously call into the michael@0: // parent process to open the file and interact with the QuotaManager. The michael@0: // child can then map the file into its address space to perform I/O. michael@0: nsRefPtr file; michael@0: if (IsMainProcess()) { michael@0: file = new SingleProcessRunnable(aPrincipal, aOpenMode, aWriteParams, michael@0: aReadParams); michael@0: } else { michael@0: file = new ChildProcessRunnable(aPrincipal, aOpenMode, aWriteParams, michael@0: aReadParams); michael@0: } michael@0: michael@0: if (!file->BlockUntilOpen(aFile)) { michael@0: return false; michael@0: } michael@0: michael@0: return file->MapMemory(aOpenMode); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: typedef uint32_t AsmJSCookieType; michael@0: static const uint32_t sAsmJSCookie = 0x600d600d; michael@0: michael@0: bool michael@0: OpenEntryForRead(nsIPrincipal* aPrincipal, michael@0: const jschar* aBegin, michael@0: const jschar* aLimit, michael@0: size_t* aSize, michael@0: const uint8_t** aMemory, michael@0: intptr_t* aFile) michael@0: { michael@0: if (size_t(aLimit - aBegin) < sMinCachedModuleLength) { michael@0: return false; michael@0: } michael@0: michael@0: ReadParams readParams; michael@0: readParams.mBegin = aBegin; michael@0: readParams.mLimit = aLimit; michael@0: michael@0: File::AutoClose file; michael@0: WriteParams notAWrite; michael@0: if (!OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &file)) { michael@0: return false; michael@0: } michael@0: michael@0: // Although we trust that the stored cache files have not been arbitrarily michael@0: // corrupted, it is possible that a previous execution aborted in the middle michael@0: // of writing a cache file (crash, OOM-killer, etc). To protect against michael@0: // partially-written cache files, we use the following scheme: michael@0: // - Allocate an extra word at the beginning of every cache file which michael@0: // starts out 0 (OpenFile opens with PR_TRUNCATE). michael@0: // - After the asm.js serialization is complete, PR_SyncMemMap to write michael@0: // everything to disk and then store a non-zero value (sAsmJSCookie) michael@0: // in the first word. michael@0: // - When attempting to read a cache file, check whether the first word is michael@0: // sAsmJSCookie. michael@0: if (file->FileSize() < sizeof(AsmJSCookieType) || michael@0: *(AsmJSCookieType*)file->MappedMemory() != sAsmJSCookie) { michael@0: return false; michael@0: } michael@0: michael@0: *aSize = file->FileSize() - sizeof(AsmJSCookieType); michael@0: *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType); michael@0: michael@0: // The caller guarnatees a call to CloseEntryForRead (on success or michael@0: // failure) at which point the file will be closed. michael@0: file.Forget(reinterpret_cast(aFile)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CloseEntryForRead(JS::Handle global, michael@0: size_t aSize, michael@0: const uint8_t* aMemory, michael@0: intptr_t aFile) michael@0: { michael@0: File::AutoClose file(reinterpret_cast(aFile)); michael@0: michael@0: MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize()); michael@0: MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory()); michael@0: } michael@0: michael@0: bool michael@0: OpenEntryForWrite(nsIPrincipal* aPrincipal, michael@0: bool aInstalled, michael@0: const jschar* aBegin, michael@0: const jschar* aEnd, michael@0: size_t aSize, michael@0: uint8_t** aMemory, michael@0: intptr_t* aFile) michael@0: { michael@0: if (size_t(aEnd - aBegin) < sMinCachedModuleLength) { michael@0: return false; michael@0: } michael@0: michael@0: // Add extra space for the AsmJSCookieType (see OpenEntryForRead). michael@0: aSize += sizeof(AsmJSCookieType); michael@0: michael@0: static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe"); michael@0: michael@0: WriteParams writeParams; michael@0: writeParams.mInstalled = aInstalled; michael@0: writeParams.mSize = aSize; michael@0: writeParams.mFastHash = HashString(aBegin, sNumFastHashChars); michael@0: writeParams.mNumChars = aEnd - aBegin; michael@0: writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars); michael@0: michael@0: File::AutoClose file; michael@0: ReadParams notARead; michael@0: if (!OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file)) { michael@0: return false; michael@0: } michael@0: michael@0: // Strip off the AsmJSCookieType from the buffer returned to the caller, michael@0: // which expects a buffer of aSize, not a buffer of sizeWithCookie starting michael@0: // with a cookie. michael@0: *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType); michael@0: michael@0: // The caller guarnatees a call to CloseEntryForWrite (on success or michael@0: // failure) at which point the file will be closed michael@0: file.Forget(reinterpret_cast(aFile)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CloseEntryForWrite(JS::Handle global, michael@0: size_t aSize, michael@0: uint8_t* aMemory, michael@0: intptr_t aFile) michael@0: { michael@0: File::AutoClose file(reinterpret_cast(aFile)); michael@0: michael@0: MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize()); michael@0: MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory()); michael@0: michael@0: // Flush to disk before writing the cookie (see OpenEntryForRead). michael@0: if (PR_SyncMemMap(file->FileDesc(), michael@0: file->MappedMemory(), michael@0: file->FileSize()) == PR_SUCCESS) { michael@0: *(AsmJSCookieType*)file->MappedMemory() = sAsmJSCookie; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: GetBuildId(JS::BuildIdCharVector* aBuildID) michael@0: { michael@0: nsCOMPtr info = do_GetService("@mozilla.org/xre/app-info;1"); michael@0: if (!info) { michael@0: return false; michael@0: } michael@0: michael@0: nsCString buildID; michael@0: nsresult rv = info->GetPlatformBuildID(buildID); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (!aBuildID->resize(buildID.Length())) { michael@0: return false; michael@0: } michael@0: michael@0: for (size_t i = 0; i < buildID.Length(); i++) { michael@0: (*aBuildID)[i] = buildID[i]; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: class Client : public quota::Client michael@0: { michael@0: public: michael@0: NS_IMETHOD_(MozExternalRefCountType) michael@0: AddRef() MOZ_OVERRIDE; michael@0: michael@0: NS_IMETHOD_(MozExternalRefCountType) michael@0: Release() MOZ_OVERRIDE; michael@0: michael@0: virtual Type michael@0: GetType() MOZ_OVERRIDE michael@0: { michael@0: return ASMJS; michael@0: } michael@0: michael@0: virtual nsresult michael@0: InitOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: UsageInfo* aUsageInfo) MOZ_OVERRIDE michael@0: { michael@0: if (!aUsageInfo) { michael@0: return NS_OK; michael@0: } michael@0: return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo); michael@0: } michael@0: michael@0: virtual nsresult michael@0: GetUsageForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: UsageInfo* aUsageInfo) MOZ_OVERRIDE michael@0: { michael@0: QuotaManager* qm = QuotaManager::Get(); michael@0: MOZ_ASSERT(qm, "We were being called by the QuotaManager"); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(directory, "We're here because the origin directory exists"); michael@0: michael@0: rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: DebugOnly exists; michael@0: MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); michael@0: michael@0: nsCOMPtr entries; michael@0: rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && michael@0: hasMore && !aUsageInfo->Canceled()) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_NOINTERFACE); michael@0: michael@0: int64_t fileSize; michael@0: rv = file->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(fileSize >= 0, "Negative size?!"); michael@0: michael@0: // Since the client is not explicitly storing files, append to database michael@0: // usage which represents implicit storage allocation. michael@0: aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual void michael@0: OnOriginClearCompleted(PersistenceType aPersistenceType, michael@0: const OriginOrPatternString& aOriginOrPattern) michael@0: MOZ_OVERRIDE michael@0: { } michael@0: michael@0: virtual void michael@0: ReleaseIOThreadObjects() MOZ_OVERRIDE michael@0: { } michael@0: michael@0: virtual bool michael@0: IsFileServiceUtilized() MOZ_OVERRIDE michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: virtual bool michael@0: IsTransactionServiceActivated() MOZ_OVERRIDE michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: virtual void michael@0: WaitForStoragesToComplete(nsTArray& aStorages, michael@0: nsIRunnable* aCallback) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("There are no storages"); michael@0: } michael@0: michael@0: virtual void michael@0: AbortTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSUME_UNREACHABLE("There are no storages"); michael@0: } michael@0: michael@0: virtual bool michael@0: HasTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: virtual void michael@0: ShutdownTransactionService() MOZ_OVERRIDE michael@0: { } michael@0: michael@0: private: michael@0: nsAutoRefCnt mRefCnt; michael@0: NS_DECL_OWNINGTHREAD michael@0: }; michael@0: michael@0: NS_IMPL_ADDREF(asmjscache::Client) michael@0: NS_IMPL_RELEASE(asmjscache::Client) michael@0: michael@0: quota::Client* michael@0: CreateClient() michael@0: { michael@0: return new Client(); michael@0: } michael@0: michael@0: } // namespace asmjscache michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: michael@0: namespace IPC { michael@0: michael@0: using mozilla::dom::asmjscache::Metadata; michael@0: using mozilla::dom::asmjscache::WriteParams; michael@0: michael@0: void michael@0: ParamTraits::Write(Message* aMsg, const paramType& aParam) michael@0: { michael@0: for (unsigned i = 0; i < Metadata::kNumEntries; i++) { michael@0: const Metadata::Entry& entry = aParam.mEntries[i]; michael@0: WriteParam(aMsg, entry.mFastHash); michael@0: WriteParam(aMsg, entry.mNumChars); michael@0: WriteParam(aMsg, entry.mFullHash); michael@0: WriteParam(aMsg, entry.mModuleIndex); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ParamTraits::Read(const Message* aMsg, void** aIter, michael@0: paramType* aResult) michael@0: { michael@0: for (unsigned i = 0; i < Metadata::kNumEntries; i++) { michael@0: Metadata::Entry& entry = aResult->mEntries[i]; michael@0: if (!ReadParam(aMsg, aIter, &entry.mFastHash) || michael@0: !ReadParam(aMsg, aIter, &entry.mNumChars) || michael@0: !ReadParam(aMsg, aIter, &entry.mFullHash) || michael@0: !ReadParam(aMsg, aIter, &entry.mModuleIndex)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ParamTraits::Log(const paramType& aParam, std::wstring* aLog) michael@0: { michael@0: for (unsigned i = 0; i < Metadata::kNumEntries; i++) { michael@0: const Metadata::Entry& entry = aParam.mEntries[i]; michael@0: LogParam(entry.mFastHash, aLog); michael@0: LogParam(entry.mNumChars, aLog); michael@0: LogParam(entry.mFullHash, aLog); michael@0: LogParam(entry.mModuleIndex, aLog); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ParamTraits::Write(Message* aMsg, const paramType& aParam) michael@0: { michael@0: WriteParam(aMsg, aParam.mSize); michael@0: WriteParam(aMsg, aParam.mFastHash); michael@0: WriteParam(aMsg, aParam.mNumChars); michael@0: WriteParam(aMsg, aParam.mFullHash); michael@0: WriteParam(aMsg, aParam.mInstalled); michael@0: } michael@0: michael@0: bool michael@0: ParamTraits::Read(const Message* aMsg, void** aIter, michael@0: paramType* aResult) michael@0: { michael@0: return ReadParam(aMsg, aIter, &aResult->mSize) && michael@0: ReadParam(aMsg, aIter, &aResult->mFastHash) && michael@0: ReadParam(aMsg, aIter, &aResult->mNumChars) && michael@0: ReadParam(aMsg, aIter, &aResult->mFullHash) && michael@0: ReadParam(aMsg, aIter, &aResult->mInstalled); michael@0: } michael@0: michael@0: void michael@0: ParamTraits::Log(const paramType& aParam, std::wstring* aLog) michael@0: { michael@0: LogParam(aParam.mSize, aLog); michael@0: LogParam(aParam.mFastHash, aLog); michael@0: LogParam(aParam.mNumChars, aLog); michael@0: LogParam(aParam.mFullHash, aLog); michael@0: LogParam(aParam.mInstalled, aLog); michael@0: } michael@0: michael@0: } // namespace IPC