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