dom/asmjscache/AsmJSCache.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/asmjscache/AsmJSCache.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1943 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "AsmJSCache.h"
    1.11 +
    1.12 +#include <stdio.h>
    1.13 +
    1.14 +#include "js/RootingAPI.h"
    1.15 +#include "jsfriendapi.h"
    1.16 +#include "mozilla/Assertions.h"
    1.17 +#include "mozilla/CondVar.h"
    1.18 +#include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
    1.19 +#include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
    1.20 +#include "mozilla/dom/ContentChild.h"
    1.21 +#include "mozilla/dom/PermissionMessageUtils.h"
    1.22 +#include "mozilla/dom/quota/Client.h"
    1.23 +#include "mozilla/dom/quota/OriginOrPatternString.h"
    1.24 +#include "mozilla/dom/quota/QuotaManager.h"
    1.25 +#include "mozilla/dom/quota/QuotaObject.h"
    1.26 +#include "mozilla/dom/quota/UsageInfo.h"
    1.27 +#include "mozilla/HashFunctions.h"
    1.28 +#include "mozilla/unused.h"
    1.29 +#include "nsIAtom.h"
    1.30 +#include "nsIFile.h"
    1.31 +#include "nsIPermissionManager.h"
    1.32 +#include "nsIPrincipal.h"
    1.33 +#include "nsIRunnable.h"
    1.34 +#include "nsISimpleEnumerator.h"
    1.35 +#include "nsIThread.h"
    1.36 +#include "nsIXULAppInfo.h"
    1.37 +#include "nsJSPrincipals.h"
    1.38 +#include "nsThreadUtils.h"
    1.39 +#include "nsXULAppAPI.h"
    1.40 +#include "prio.h"
    1.41 +#include "private/pprio.h"
    1.42 +
    1.43 +#define ASMJSCACHE_METADATA_FILE_NAME "metadata"
    1.44 +#define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
    1.45 +
    1.46 +using mozilla::dom::quota::AssertIsOnIOThread;
    1.47 +using mozilla::dom::quota::OriginOrPatternString;
    1.48 +using mozilla::dom::quota::PersistenceType;
    1.49 +using mozilla::dom::quota::QuotaManager;
    1.50 +using mozilla::dom::quota::QuotaObject;
    1.51 +using mozilla::dom::quota::UsageInfo;
    1.52 +using mozilla::unused;
    1.53 +using mozilla::HashString;
    1.54 +
    1.55 +namespace mozilla {
    1.56 +
    1.57 +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
    1.58 +
    1.59 +namespace dom {
    1.60 +namespace asmjscache {
    1.61 +
    1.62 +namespace {
    1.63 +
    1.64 +bool
    1.65 +IsMainProcess()
    1.66 +{
    1.67 +  return XRE_GetProcessType() == GeckoProcessType_Default;
    1.68 +}
    1.69 +
    1.70 +// Anything smaller should compile fast enough that caching will just add
    1.71 +// overhead.
    1.72 +static const size_t sMinCachedModuleLength = 10000;
    1.73 +
    1.74 +// The number of characters to hash into the Metadata::Entry::mFastHash.
    1.75 +static const unsigned sNumFastHashChars = 4096;
    1.76 +
    1.77 +nsresult
    1.78 +WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata)
    1.79 +{
    1.80 +  int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;
    1.81 +
    1.82 +  JS::BuildIdCharVector buildId;
    1.83 +  bool ok = GetBuildId(&buildId);
    1.84 +  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
    1.85 +
    1.86 +  ScopedPRFileDesc fd;
    1.87 +  nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
    1.88 +  NS_ENSURE_SUCCESS(rv, rv);
    1.89 +
    1.90 +  uint32_t length = buildId.length();
    1.91 +  int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
    1.92 +  NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);
    1.93 +
    1.94 +  bytesWritten = PR_Write(fd, buildId.begin(), length);
    1.95 +  NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);
    1.96 +
    1.97 +  bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
    1.98 +  NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
    1.99 +
   1.100 +  return NS_OK;
   1.101 +}
   1.102 +
   1.103 +nsresult
   1.104 +ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata)
   1.105 +{
   1.106 +  int32_t openFlags = PR_RDONLY;
   1.107 +
   1.108 +  ScopedPRFileDesc fd;
   1.109 +  nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
   1.110 +  NS_ENSURE_SUCCESS(rv, rv);
   1.111 +
   1.112 +  // Read the buildid and check that it matches the current buildid
   1.113 +
   1.114 +  JS::BuildIdCharVector currentBuildId;
   1.115 +  bool ok = GetBuildId(&currentBuildId);
   1.116 +  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
   1.117 +
   1.118 +  uint32_t length;
   1.119 +  int32_t bytesRead = PR_Read(fd, &length, sizeof(length));
   1.120 +  NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED);
   1.121 +
   1.122 +  NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED);
   1.123 +
   1.124 +  JS::BuildIdCharVector fileBuildId;
   1.125 +  ok = fileBuildId.resize(length);
   1.126 +  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
   1.127 +
   1.128 +  bytesRead = PR_Read(fd, fileBuildId.begin(), length);
   1.129 +  NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED);
   1.130 +
   1.131 +  for (uint32_t i = 0; i < length; i++) {
   1.132 +    if (currentBuildId[i] != fileBuildId[i]) {
   1.133 +      return NS_ERROR_FAILURE;
   1.134 +    }
   1.135 +  }
   1.136 +
   1.137 +  // Read the Metadata struct
   1.138 +
   1.139 +  bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata));
   1.140 +  NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
   1.141 +
   1.142 +  return NS_OK;
   1.143 +}
   1.144 +
   1.145 +nsresult
   1.146 +GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile)
   1.147 +{
   1.148 +  nsCOMPtr<nsIFile> cacheFile;
   1.149 +  nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
   1.150 +  NS_ENSURE_SUCCESS(rv, rv);
   1.151 +
   1.152 +  nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
   1.153 +  cacheFileName.AppendInt(aModuleIndex);
   1.154 +  rv = cacheFile->Append(cacheFileName);
   1.155 +  NS_ENSURE_SUCCESS(rv, rv);
   1.156 +
   1.157 +  cacheFile.forget(aCacheFile);
   1.158 +  return NS_OK;
   1.159 +}
   1.160 +
   1.161 +class AutoDecreaseUsageForOrigin
   1.162 +{
   1.163 +  const nsACString& mGroup;
   1.164 +  const nsACString& mOrigin;
   1.165 +
   1.166 +public:
   1.167 +  uint64_t mFreed;
   1.168 +
   1.169 +  AutoDecreaseUsageForOrigin(const nsACString& aGroup,
   1.170 +                             const nsACString& aOrigin)
   1.171 +
   1.172 +  : mGroup(aGroup),
   1.173 +    mOrigin(aOrigin),
   1.174 +    mFreed(0)
   1.175 +  { }
   1.176 +
   1.177 +  ~AutoDecreaseUsageForOrigin()
   1.178 +  {
   1.179 +    AssertIsOnIOThread();
   1.180 +
   1.181 +    if (!mFreed) {
   1.182 +      return;
   1.183 +    }
   1.184 +
   1.185 +    QuotaManager* qm = QuotaManager::Get();
   1.186 +    MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   1.187 +
   1.188 +    qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY,
   1.189 +                               mGroup, mOrigin, mFreed);
   1.190 +  }
   1.191 +};
   1.192 +
   1.193 +static void
   1.194 +EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
   1.195 +             const nsACString& aOrigin, uint64_t aNumBytes,
   1.196 +             Metadata& aMetadata)
   1.197 +{
   1.198 +  AssertIsOnIOThread();
   1.199 +
   1.200 +  AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);
   1.201 +
   1.202 +  for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
   1.203 +    Metadata::Entry& entry = aMetadata.mEntries[i];
   1.204 +    unsigned moduleIndex = entry.mModuleIndex;
   1.205 +
   1.206 +    nsCOMPtr<nsIFile> file;
   1.207 +    nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
   1.208 +    if (NS_WARN_IF(NS_FAILED(rv))) {
   1.209 +      return;
   1.210 +    }
   1.211 +
   1.212 +    bool exists;
   1.213 +    rv = file->Exists(&exists);
   1.214 +    if (NS_WARN_IF(NS_FAILED(rv))) {
   1.215 +      return;
   1.216 +    }
   1.217 +
   1.218 +    if (exists) {
   1.219 +      int64_t fileSize;
   1.220 +      rv = file->GetFileSize(&fileSize);
   1.221 +      if (NS_WARN_IF(NS_FAILED(rv))) {
   1.222 +        return;
   1.223 +      }
   1.224 +
   1.225 +      rv = file->Remove(false);
   1.226 +      if (NS_WARN_IF(NS_FAILED(rv))) {
   1.227 +        return;
   1.228 +      }
   1.229 +
   1.230 +      usage.mFreed += fileSize;
   1.231 +    }
   1.232 +
   1.233 +    entry.clear();
   1.234 +  }
   1.235 +}
   1.236 +
   1.237 +// FileDescriptorHolder owns a file descriptor and its memory mapping.
   1.238 +// FileDescriptorHolder is derived by all three runnable classes (that is,
   1.239 +// (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds,
   1.240 +// FileDescriptorHolder is derived virtually by File and MainProcessRunnable for
   1.241 +// the benefit of SingleProcessRunnable, which derives both. Since File and
   1.242 +// MainProcessRunnable both need to be runnables, FileDescriptorHolder also
   1.243 +// derives nsRunnable.
   1.244 +class FileDescriptorHolder : public nsRunnable
   1.245 +{
   1.246 +public:
   1.247 +  FileDescriptorHolder()
   1.248 +  : mQuotaObject(nullptr),
   1.249 +    mFileSize(INT64_MIN),
   1.250 +    mFileDesc(nullptr),
   1.251 +    mFileMap(nullptr),
   1.252 +    mMappedMemory(nullptr)
   1.253 +  { }
   1.254 +
   1.255 +  ~FileDescriptorHolder()
   1.256 +  {
   1.257 +    // These resources should have already been released by Finish().
   1.258 +    MOZ_ASSERT(!mQuotaObject);
   1.259 +    MOZ_ASSERT(!mMappedMemory);
   1.260 +    MOZ_ASSERT(!mFileMap);
   1.261 +    MOZ_ASSERT(!mFileDesc);
   1.262 +  }
   1.263 +
   1.264 +  size_t
   1.265 +  FileSize() const
   1.266 +  {
   1.267 +    MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
   1.268 +    return mFileSize;
   1.269 +  }
   1.270 +
   1.271 +  PRFileDesc*
   1.272 +  FileDesc() const
   1.273 +  {
   1.274 +    MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
   1.275 +    return mFileDesc;
   1.276 +  }
   1.277 +
   1.278 +  bool
   1.279 +  MapMemory(OpenMode aOpenMode)
   1.280 +  {
   1.281 +    MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");
   1.282 +
   1.283 +    PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY
   1.284 +                                                          : PR_PROT_READWRITE;
   1.285 +
   1.286 +    mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
   1.287 +    NS_ENSURE_TRUE(mFileMap, false);
   1.288 +
   1.289 +    mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
   1.290 +    NS_ENSURE_TRUE(mMappedMemory, false);
   1.291 +
   1.292 +    return true;
   1.293 +  }
   1.294 +
   1.295 +  void*
   1.296 +  MappedMemory() const
   1.297 +  {
   1.298 +    MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
   1.299 +    return mMappedMemory;
   1.300 +  }
   1.301 +
   1.302 +protected:
   1.303 +  // This method must be called before AllowNextSynchronizedOp (which releases
   1.304 +  // the lock protecting these resources). It is idempotent, so it is ok to call
   1.305 +  // multiple times (or before the file has been fully opened).
   1.306 +  void
   1.307 +  Finish()
   1.308 +  {
   1.309 +    if (mMappedMemory) {
   1.310 +      PR_MemUnmap(mMappedMemory, mFileSize);
   1.311 +      mMappedMemory = nullptr;
   1.312 +    }
   1.313 +    if (mFileMap) {
   1.314 +      PR_CloseFileMap(mFileMap);
   1.315 +      mFileMap = nullptr;
   1.316 +    }
   1.317 +    if (mFileDesc) {
   1.318 +      PR_Close(mFileDesc);
   1.319 +      mFileDesc = nullptr;
   1.320 +    }
   1.321 +
   1.322 +    // Holding the QuotaObject alive until all the cache files are closed enables
   1.323 +    // assertions in QuotaManager that the cache entry isn't cleared while we
   1.324 +    // are working on it.
   1.325 +    mQuotaObject = nullptr;
   1.326 +  }
   1.327 +
   1.328 +  nsRefPtr<QuotaObject> mQuotaObject;
   1.329 +  int64_t mFileSize;
   1.330 +  PRFileDesc* mFileDesc;
   1.331 +  PRFileMap* mFileMap;
   1.332 +  void* mMappedMemory;
   1.333 +};
   1.334 +
   1.335 +// File is a base class shared by (Single|Client)ProcessEntryRunnable that
   1.336 +// presents a single interface to the AsmJSCache ops which need to wait until
   1.337 +// the file is open, regardless of whether we are executing in the main process
   1.338 +// or not.
   1.339 +class File : public virtual FileDescriptorHolder
   1.340 +{
   1.341 +public:
   1.342 +  class AutoClose
   1.343 +  {
   1.344 +    File* mFile;
   1.345 +
   1.346 +  public:
   1.347 +    explicit AutoClose(File* aFile = nullptr)
   1.348 +    : mFile(aFile)
   1.349 +    { }
   1.350 +
   1.351 +    void
   1.352 +    Init(File* aFile)
   1.353 +    {
   1.354 +      MOZ_ASSERT(!mFile);
   1.355 +      mFile = aFile;
   1.356 +    }
   1.357 +
   1.358 +    File*
   1.359 +    operator->() const
   1.360 +    {
   1.361 +      MOZ_ASSERT(mFile);
   1.362 +      return mFile;
   1.363 +    }
   1.364 +
   1.365 +    void
   1.366 +    Forget(File** aFile)
   1.367 +    {
   1.368 +      *aFile = mFile;
   1.369 +      mFile = nullptr;
   1.370 +    }
   1.371 +
   1.372 +    ~AutoClose()
   1.373 +    {
   1.374 +      if (mFile) {
   1.375 +        mFile->Close();
   1.376 +      }
   1.377 +    }
   1.378 +  };
   1.379 +
   1.380 +  bool
   1.381 +  BlockUntilOpen(AutoClose* aCloser)
   1.382 +  {
   1.383 +    MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
   1.384 +    MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");
   1.385 +
   1.386 +    mWaiting = true;
   1.387 +
   1.388 +    nsresult rv = NS_DispatchToMainThread(this);
   1.389 +    NS_ENSURE_SUCCESS(rv, false);
   1.390 +
   1.391 +    {
   1.392 +      MutexAutoLock lock(mMutex);
   1.393 +      while (mWaiting) {
   1.394 +        mCondVar.Wait();
   1.395 +      }
   1.396 +    }
   1.397 +
   1.398 +    if (!mOpened) {
   1.399 +      return false;
   1.400 +    }
   1.401 +
   1.402 +    // Now that we're open, we're guarnateed a Close() call. However, we are
   1.403 +    // not guarnateed someone is holding an outstanding reference until the File
   1.404 +    // is closed, so we do that ourselves and Release() in OnClose().
   1.405 +    aCloser->Init(this);
   1.406 +    AddRef();
   1.407 +    return true;
   1.408 +  }
   1.409 +
   1.410 +  // This method must be called if BlockUntilOpen returns 'true'. AutoClose
   1.411 +  // mostly takes care of this. A derived class that implements Close() must
   1.412 +  // guarnatee that OnClose() is called (eventually).
   1.413 +  virtual void
   1.414 +  Close() = 0;
   1.415 +
   1.416 +protected:
   1.417 +  File()
   1.418 +  : mMutex("File::mMutex"),
   1.419 +    mCondVar(mMutex, "File::mCondVar"),
   1.420 +    mWaiting(false),
   1.421 +    mOpened(false)
   1.422 +  { }
   1.423 +
   1.424 +  ~File()
   1.425 +  {
   1.426 +    MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
   1.427 +    MOZ_ASSERT(!mOpened, "OnClose() should have been called");
   1.428 +  }
   1.429 +
   1.430 +  void
   1.431 +  OnOpen()
   1.432 +  {
   1.433 +    Notify(true);
   1.434 +  }
   1.435 +
   1.436 +  void
   1.437 +  OnFailure()
   1.438 +  {
   1.439 +    FileDescriptorHolder::Finish();
   1.440 +
   1.441 +    Notify(false);
   1.442 +  }
   1.443 +
   1.444 +  void
   1.445 +  OnClose()
   1.446 +  {
   1.447 +    FileDescriptorHolder::Finish();
   1.448 +
   1.449 +    MOZ_ASSERT(mOpened);
   1.450 +    mOpened = false;
   1.451 +
   1.452 +    // Match the AddRef in BlockUntilOpen(). The main thread event loop still
   1.453 +    // holds an outstanding ref which will keep 'this' alive until returning to
   1.454 +    // the event loop.
   1.455 +    Release();
   1.456 +  }
   1.457 +
   1.458 +private:
   1.459 +  void
   1.460 +  Notify(bool aSuccess)
   1.461 +  {
   1.462 +    MOZ_ASSERT(NS_IsMainThread());
   1.463 +
   1.464 +    MutexAutoLock lock(mMutex);
   1.465 +    MOZ_ASSERT(mWaiting);
   1.466 +
   1.467 +    mWaiting = false;
   1.468 +    mOpened = aSuccess;
   1.469 +    mCondVar.Notify();
   1.470 +  }
   1.471 +
   1.472 +  Mutex mMutex;
   1.473 +  CondVar mCondVar;
   1.474 +  bool mWaiting;
   1.475 +  bool mOpened;
   1.476 +};
   1.477 +
   1.478 +// MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
   1.479 +// that factors out the runnable state machine required to open a cache entry
   1.480 +// that runs in the main process.
   1.481 +class MainProcessRunnable : public virtual FileDescriptorHolder
   1.482 +{
   1.483 +public:
   1.484 +  NS_DECL_NSIRUNNABLE
   1.485 +
   1.486 +  // MainProcessRunnable runnable assumes that the derived class ensures
   1.487 +  // (through ref-counting or preconditions) that aPrincipal is kept alive for
   1.488 +  // the lifetime of the MainProcessRunnable.
   1.489 +  MainProcessRunnable(nsIPrincipal* aPrincipal,
   1.490 +                      OpenMode aOpenMode,
   1.491 +                      WriteParams aWriteParams)
   1.492 +  : mPrincipal(aPrincipal),
   1.493 +    mOpenMode(aOpenMode),
   1.494 +    mWriteParams(aWriteParams),
   1.495 +    mNeedAllowNextSynchronizedOp(false),
   1.496 +    mPersistence(quota::PERSISTENCE_TYPE_INVALID),
   1.497 +    mState(eInitial)
   1.498 +  {
   1.499 +    MOZ_ASSERT(IsMainProcess());
   1.500 +  }
   1.501 +
   1.502 +  virtual ~MainProcessRunnable()
   1.503 +  {
   1.504 +    MOZ_ASSERT(mState == eFinished);
   1.505 +    MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
   1.506 +  }
   1.507 +
   1.508 +protected:
   1.509 +  // This method is called by the derived class on the main thread when a
   1.510 +  // cache entry has been selected to open.
   1.511 +  void
   1.512 +  OpenForRead(unsigned aModuleIndex)
   1.513 +  {
   1.514 +    MOZ_ASSERT(NS_IsMainThread());
   1.515 +    MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
   1.516 +    MOZ_ASSERT(mOpenMode == eOpenForRead);
   1.517 +
   1.518 +    mModuleIndex = aModuleIndex;
   1.519 +    mState = eReadyToOpenCacheFileForRead;
   1.520 +    DispatchToIOThread();
   1.521 +  }
   1.522 +
   1.523 +  // This method is called by the derived class on the main thread when no cache
   1.524 +  // entry was found to open. If we just tried a lookup in persistent storage
   1.525 +  // then we might still get a hit in temporary storage (for an asm.js module
   1.526 +  // that wasn't compiled at install-time).
   1.527 +  void
   1.528 +  CacheMiss()
   1.529 +  {
   1.530 +    MOZ_ASSERT(NS_IsMainThread());
   1.531 +    MOZ_ASSERT(mState == eFailedToReadMetadata ||
   1.532 +               mState == eWaitingToOpenCacheFileForRead);
   1.533 +    MOZ_ASSERT(mOpenMode == eOpenForRead);
   1.534 +
   1.535 +    if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   1.536 +      Fail();
   1.537 +      return;
   1.538 +    }
   1.539 +
   1.540 +    // Try again with a clean slate. InitOnMainThread will see that mPersistence
   1.541 +    // is initialized and switch to temporary storage.
   1.542 +    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
   1.543 +    FinishOnMainThread();
   1.544 +    mState = eInitial;
   1.545 +    NS_DispatchToMainThread(this);
   1.546 +  }
   1.547 +
   1.548 +  // This method is called by the derived class (either on the JS compilation
   1.549 +  // thread or the main thread) when the JS engine is finished reading/writing
   1.550 +  // the cache entry.
   1.551 +  void
   1.552 +  Close()
   1.553 +  {
   1.554 +    MOZ_ASSERT(mState == eOpened);
   1.555 +    mState = eClosing;
   1.556 +    NS_DispatchToMainThread(this);
   1.557 +  }
   1.558 +
   1.559 +  // This method is called both internally and by derived classes upon any
   1.560 +  // failure that prevents the eventual opening of the cache entry.
   1.561 +  void
   1.562 +  Fail()
   1.563 +  {
   1.564 +    MOZ_ASSERT(mState != eOpened &&
   1.565 +               mState != eClosing &&
   1.566 +               mState != eFailing &&
   1.567 +               mState != eFinished);
   1.568 +
   1.569 +    mState = eFailing;
   1.570 +    NS_DispatchToMainThread(this);
   1.571 +  }
   1.572 +
   1.573 +  // Called by MainProcessRunnable on the main thread after metadata is open:
   1.574 +  virtual void
   1.575 +  OnOpenMetadataForRead(const Metadata& aMetadata) = 0;
   1.576 +
   1.577 +  // Called by MainProcessRunnable on the main thread after the entry is open:
   1.578 +  virtual void
   1.579 +  OnOpenCacheFile() = 0;
   1.580 +
   1.581 +  // This method may be overridden, but it must be called from the overrider.
   1.582 +  // Called by MainProcessRunnable on the main thread after a call to Fail():
   1.583 +  virtual void
   1.584 +  OnFailure()
   1.585 +  {
   1.586 +    FinishOnMainThread();
   1.587 +  }
   1.588 +
   1.589 +  // This method may be overridden, but it must be called from the overrider.
   1.590 +  // Called by MainProcessRunnable on the main thread after a call to Close():
   1.591 +  virtual void
   1.592 +  OnClose()
   1.593 +  {
   1.594 +    FinishOnMainThread();
   1.595 +  }
   1.596 +
   1.597 +private:
   1.598 +  nsresult
   1.599 +  InitOnMainThread();
   1.600 +
   1.601 +  nsresult
   1.602 +  ReadMetadata();
   1.603 +
   1.604 +  nsresult
   1.605 +  OpenCacheFileForWrite();
   1.606 +
   1.607 +  nsresult
   1.608 +  OpenCacheFileForRead();
   1.609 +
   1.610 +  void
   1.611 +  FinishOnMainThread();
   1.612 +
   1.613 +  void
   1.614 +  DispatchToIOThread()
   1.615 +  {
   1.616 +    // If shutdown just started, the QuotaManager may have been deleted.
   1.617 +    QuotaManager* qm = QuotaManager::Get();
   1.618 +    if (!qm) {
   1.619 +      Fail();
   1.620 +      return;
   1.621 +    }
   1.622 +
   1.623 +    nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
   1.624 +    if (NS_FAILED(rv)) {
   1.625 +      Fail();
   1.626 +      return;
   1.627 +    }
   1.628 +  }
   1.629 +
   1.630 +  nsIPrincipal* const mPrincipal;
   1.631 +  const OpenMode mOpenMode;
   1.632 +  const WriteParams mWriteParams;
   1.633 +
   1.634 +  // State initialized during eInitial:
   1.635 +  bool mNeedAllowNextSynchronizedOp;
   1.636 +  quota::PersistenceType mPersistence;
   1.637 +  nsCString mGroup;
   1.638 +  nsCString mOrigin;
   1.639 +  nsCString mStorageId;
   1.640 +
   1.641 +  // State initialized during eReadyToReadMetadata
   1.642 +  nsCOMPtr<nsIFile> mDirectory;
   1.643 +  nsCOMPtr<nsIFile> mMetadataFile;
   1.644 +  Metadata mMetadata;
   1.645 +
   1.646 +  // State initialized during eWaitingToOpenCacheFileForRead
   1.647 +  unsigned mModuleIndex;
   1.648 +
   1.649 +  enum State {
   1.650 +    eInitial, // Just created, waiting to be dispatched to main thread
   1.651 +    eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
   1.652 +    eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
   1.653 +    eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
   1.654 +    eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
   1.655 +    eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
   1.656 +    eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
   1.657 +    eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
   1.658 +    eOpened, // Finished calling OnOpen, waiting to be closed
   1.659 +    eClosing, // Waiting to be dispatched to main thread again
   1.660 +    eFailing, // Just failed, waiting to be dispatched to the main thread
   1.661 +    eFinished, // Terminal state
   1.662 +  };
   1.663 +  State mState;
   1.664 +};
   1.665 +
   1.666 +nsresult
   1.667 +MainProcessRunnable::InitOnMainThread()
   1.668 +{
   1.669 +  MOZ_ASSERT(NS_IsMainThread());
   1.670 +  MOZ_ASSERT(mState == eInitial);
   1.671 +
   1.672 +  QuotaManager* qm = QuotaManager::GetOrCreate();
   1.673 +  NS_ENSURE_STATE(qm);
   1.674 +
   1.675 +  nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup,
   1.676 +                                                   &mOrigin, nullptr, nullptr);
   1.677 +  NS_ENSURE_SUCCESS(rv, rv);
   1.678 +
   1.679 +  bool isApp = mPrincipal->GetAppStatus() !=
   1.680 +               nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   1.681 +
   1.682 +  if (mOpenMode == eOpenForWrite) {
   1.683 +    MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
   1.684 +    if (mWriteParams.mInstalled) {
   1.685 +      // If we are performing install-time caching of an app, we'd like to store
   1.686 +      // the cache entry in persistent storage so the entry is never evicted,
   1.687 +      // but we need to verify that the app has unlimited storage permissions
   1.688 +      // first. Unlimited storage permissions justify us in skipping all quota
   1.689 +      // checks when storing the cache entry and avoids all the issues around
   1.690 +      // the persistent quota prompt.
   1.691 +      MOZ_ASSERT(isApp);
   1.692 +
   1.693 +      nsCOMPtr<nsIPermissionManager> pm =
   1.694 +        do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
   1.695 +      NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
   1.696 +
   1.697 +      uint32_t permission;
   1.698 +      rv = pm->TestPermissionFromPrincipal(mPrincipal,
   1.699 +                                           PERMISSION_STORAGE_UNLIMITED,
   1.700 +                                           &permission);
   1.701 +      NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
   1.702 +
   1.703 +      // If app doens't have the unlimited storage permission, we can still
   1.704 +      // cache in temporary for a likely good first-run experience.
   1.705 +      mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
   1.706 +                     ? quota::PERSISTENCE_TYPE_PERSISTENT
   1.707 +                     : quota::PERSISTENCE_TYPE_TEMPORARY;
   1.708 +    } else {
   1.709 +      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
   1.710 +    }
   1.711 +  } else {
   1.712 +    // For the reasons described above, apps may have cache entries in both
   1.713 +    // persistent and temporary storage. At lookup time we don't know how and
   1.714 +    // where the given script was cached, so start the search in persistent
   1.715 +    // storage and, if that fails, search in temporary storage. (Non-apps can
   1.716 +    // only be stored in temporary storage.)
   1.717 +    if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
   1.718 +      mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
   1.719 +                           : quota::PERSISTENCE_TYPE_TEMPORARY;
   1.720 +    } else {
   1.721 +      MOZ_ASSERT(isApp);
   1.722 +      MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
   1.723 +      mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
   1.724 +    }
   1.725 +  }
   1.726 +
   1.727 +  QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
   1.728 +                             NS_LITERAL_STRING("asmjs"), mStorageId);
   1.729 +
   1.730 +  return NS_OK;
   1.731 +}
   1.732 +
   1.733 +nsresult
   1.734 +MainProcessRunnable::ReadMetadata()
   1.735 +{
   1.736 +  AssertIsOnIOThread();
   1.737 +  MOZ_ASSERT(mState == eReadyToReadMetadata);
   1.738 +
   1.739 +  QuotaManager* qm = QuotaManager::Get();
   1.740 +  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   1.741 +
   1.742 +  // Only track quota for temporary storage. For persistent storage, we've
   1.743 +  // already checked that we have unlimited-storage permissions.
   1.744 +  bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
   1.745 +
   1.746 +  nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
   1.747 +                                              trackQuota,
   1.748 +                                              getter_AddRefs(mDirectory));
   1.749 +  NS_ENSURE_SUCCESS(rv, rv);
   1.750 +
   1.751 +  rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
   1.752 +  NS_ENSURE_SUCCESS(rv, rv);
   1.753 +
   1.754 +  bool exists;
   1.755 +  rv = mDirectory->Exists(&exists);
   1.756 +  NS_ENSURE_SUCCESS(rv, rv);
   1.757 +
   1.758 +  if (!exists) {
   1.759 +    rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
   1.760 +    NS_ENSURE_SUCCESS(rv, rv);
   1.761 +  } else {
   1.762 +    DebugOnly<bool> isDirectory;
   1.763 +    MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory)));
   1.764 +    MOZ_ASSERT(isDirectory, "Should have caught this earlier!");
   1.765 +  }
   1.766 +
   1.767 +  rv = mDirectory->Clone(getter_AddRefs(mMetadataFile));
   1.768 +  NS_ENSURE_SUCCESS(rv, rv);
   1.769 +
   1.770 +  rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME));
   1.771 +  NS_ENSURE_SUCCESS(rv, rv);
   1.772 +
   1.773 +  rv = mMetadataFile->Exists(&exists);
   1.774 +  NS_ENSURE_SUCCESS(rv, rv);
   1.775 +
   1.776 +  if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) {
   1.777 +    exists = false;
   1.778 +  }
   1.779 +
   1.780 +  if (!exists) {
   1.781 +    // If we are reading, we can't possibly have a cache hit.
   1.782 +    if (mOpenMode == eOpenForRead) {
   1.783 +      return NS_ERROR_FILE_NOT_FOUND;
   1.784 +    }
   1.785 +
   1.786 +    // Initialize Metadata with a valid empty state for the LRU cache.
   1.787 +    for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
   1.788 +      Metadata::Entry& entry = mMetadata.mEntries[i];
   1.789 +      entry.mModuleIndex = i;
   1.790 +      entry.clear();
   1.791 +    }
   1.792 +  }
   1.793 +
   1.794 +  return NS_OK;
   1.795 +}
   1.796 +
   1.797 +nsresult
   1.798 +MainProcessRunnable::OpenCacheFileForWrite()
   1.799 +{
   1.800 +  AssertIsOnIOThread();
   1.801 +  MOZ_ASSERT(mState == eReadyToReadMetadata);
   1.802 +  MOZ_ASSERT(mOpenMode == eOpenForWrite);
   1.803 +
   1.804 +  mFileSize = mWriteParams.mSize;
   1.805 +
   1.806 +  // Kick out the oldest entry in the LRU queue in the metadata.
   1.807 +  mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;
   1.808 +
   1.809 +  nsCOMPtr<nsIFile> file;
   1.810 +  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   1.811 +  NS_ENSURE_SUCCESS(rv, rv);
   1.812 +
   1.813 +  QuotaManager* qm = QuotaManager::Get();
   1.814 +  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   1.815 +
   1.816 +  // If we are allocating in temporary storage, ask the QuotaManager if we're
   1.817 +  // within the quota. If we are allocating in persistent storage, we've already
   1.818 +  // checked that we have the unlimited-storage permission, so there is nothing
   1.819 +  // to check.
   1.820 +  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   1.821 +    // Create the QuotaObject before all file IO and keep it alive until caching
   1.822 +    // completes to get maximum assertion coverage in QuotaManager against
   1.823 +    // concurrent removal, etc.
   1.824 +    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
   1.825 +    NS_ENSURE_STATE(mQuotaObject);
   1.826 +
   1.827 +    if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
   1.828 +      // If the request fails, it might be because mOrigin is using too much
   1.829 +      // space (MaybeAllocateMoreSpace will not evict our own origin since it is
   1.830 +      // active). Try to make some space by evicting LRU entries until there is
   1.831 +      // enough space.
   1.832 +      EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
   1.833 +      if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
   1.834 +        return NS_ERROR_FAILURE;
   1.835 +      }
   1.836 +    }
   1.837 +  }
   1.838 +
   1.839 +  int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
   1.840 +  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   1.841 +  NS_ENSURE_SUCCESS(rv, rv);
   1.842 +
   1.843 +  // Move the mModuleIndex's LRU entry to the recent end of the queue.
   1.844 +  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
   1.845 +  Metadata::Entry& entry = mMetadata.mEntries[0];
   1.846 +  entry.mFastHash = mWriteParams.mFastHash;
   1.847 +  entry.mNumChars = mWriteParams.mNumChars;
   1.848 +  entry.mFullHash = mWriteParams.mFullHash;
   1.849 +  entry.mModuleIndex = mModuleIndex;
   1.850 +
   1.851 +  rv = WriteMetadataFile(mMetadataFile, mMetadata);
   1.852 +  NS_ENSURE_SUCCESS(rv, rv);
   1.853 +
   1.854 +  return NS_OK;
   1.855 +}
   1.856 +
   1.857 +nsresult
   1.858 +MainProcessRunnable::OpenCacheFileForRead()
   1.859 +{
   1.860 +  AssertIsOnIOThread();
   1.861 +  MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
   1.862 +  MOZ_ASSERT(mOpenMode == eOpenForRead);
   1.863 +
   1.864 +  nsCOMPtr<nsIFile> file;
   1.865 +  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
   1.866 +  NS_ENSURE_SUCCESS(rv, rv);
   1.867 +
   1.868 +  QuotaManager* qm = QuotaManager::Get();
   1.869 +  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
   1.870 +
   1.871 +  if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
   1.872 +    // Even though it's not strictly necessary, create the QuotaObject before
   1.873 +    // all file IO and keep it alive until caching completes to get maximum
   1.874 +    // assertion coverage in QuotaManager against concurrent removal, etc.
   1.875 +    mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
   1.876 +    NS_ENSURE_STATE(mQuotaObject);
   1.877 +  }
   1.878 +
   1.879 +  rv = file->GetFileSize(&mFileSize);
   1.880 +  NS_ENSURE_SUCCESS(rv, rv);
   1.881 +
   1.882 +  int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
   1.883 +  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
   1.884 +  NS_ENSURE_SUCCESS(rv, rv);
   1.885 +
   1.886 +  // Move the mModuleIndex's LRU entry to the recent end of the queue.
   1.887 +  unsigned lruIndex = 0;
   1.888 +  while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
   1.889 +    if (++lruIndex == Metadata::kNumEntries) {
   1.890 +      return NS_ERROR_UNEXPECTED;
   1.891 +    }
   1.892 +  }
   1.893 +  Metadata::Entry entry = mMetadata.mEntries[lruIndex];
   1.894 +  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
   1.895 +  mMetadata.mEntries[0] = entry;
   1.896 +
   1.897 +  rv = WriteMetadataFile(mMetadataFile, mMetadata);
   1.898 +  NS_ENSURE_SUCCESS(rv, rv);
   1.899 +
   1.900 +  return NS_OK;
   1.901 +}
   1.902 +
   1.903 +void
   1.904 +MainProcessRunnable::FinishOnMainThread()
   1.905 +{
   1.906 +  MOZ_ASSERT(NS_IsMainThread());
   1.907 +
   1.908 +  // Per FileDescriptorHolder::Finish()'s comment, call before
   1.909 +  // AllowNextSynchronizedOp.
   1.910 +  FileDescriptorHolder::Finish();
   1.911 +
   1.912 +  if (mNeedAllowNextSynchronizedOp) {
   1.913 +    mNeedAllowNextSynchronizedOp = false;
   1.914 +    QuotaManager* qm = QuotaManager::Get();
   1.915 +    if (qm) {
   1.916 +      qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
   1.917 +                                  Nullable<PersistenceType>(mPersistence),
   1.918 +                                  mStorageId);
   1.919 +    }
   1.920 +  }
   1.921 +}
   1.922 +
   1.923 +NS_IMETHODIMP
   1.924 +MainProcessRunnable::Run()
   1.925 +{
   1.926 +  nsresult rv;
   1.927 +
   1.928 +  // All success/failure paths must eventually call Finish() to avoid leaving
   1.929 +  // the parser hanging.
   1.930 +  switch (mState) {
   1.931 +    case eInitial: {
   1.932 +      MOZ_ASSERT(NS_IsMainThread());
   1.933 +
   1.934 +      rv = InitOnMainThread();
   1.935 +      if (NS_FAILED(rv)) {
   1.936 +        Fail();
   1.937 +        return NS_OK;
   1.938 +      }
   1.939 +
   1.940 +      mState = eWaitingToOpenMetadata;
   1.941 +      rv = QuotaManager::Get()->WaitForOpenAllowed(
   1.942 +                                     OriginOrPatternString::FromOrigin(mOrigin),
   1.943 +                                     Nullable<PersistenceType>(mPersistence),
   1.944 +                                     mStorageId, this);
   1.945 +      if (NS_FAILED(rv)) {
   1.946 +        Fail();
   1.947 +        return NS_OK;
   1.948 +      }
   1.949 +
   1.950 +      mNeedAllowNextSynchronizedOp = true;
   1.951 +      return NS_OK;
   1.952 +    }
   1.953 +
   1.954 +    case eWaitingToOpenMetadata: {
   1.955 +      MOZ_ASSERT(NS_IsMainThread());
   1.956 +
   1.957 +      mState = eReadyToReadMetadata;
   1.958 +      DispatchToIOThread();
   1.959 +      return NS_OK;
   1.960 +    }
   1.961 +
   1.962 +    case eReadyToReadMetadata: {
   1.963 +      AssertIsOnIOThread();
   1.964 +
   1.965 +      rv = ReadMetadata();
   1.966 +      if (NS_FAILED(rv)) {
   1.967 +        mState = eFailedToReadMetadata;
   1.968 +        NS_DispatchToMainThread(this);
   1.969 +        return NS_OK;
   1.970 +      }
   1.971 +
   1.972 +      if (mOpenMode == eOpenForRead) {
   1.973 +        mState = eSendingMetadataForRead;
   1.974 +        NS_DispatchToMainThread(this);
   1.975 +        return NS_OK;
   1.976 +      }
   1.977 +
   1.978 +      rv = OpenCacheFileForWrite();
   1.979 +      if (NS_FAILED(rv)) {
   1.980 +        Fail();
   1.981 +        return NS_OK;
   1.982 +      }
   1.983 +
   1.984 +      mState = eSendingCacheFile;
   1.985 +      NS_DispatchToMainThread(this);
   1.986 +      return NS_OK;
   1.987 +    }
   1.988 +
   1.989 +    case eFailedToReadMetadata: {
   1.990 +      MOZ_ASSERT(NS_IsMainThread());
   1.991 +
   1.992 +      CacheMiss();
   1.993 +      return NS_OK;
   1.994 +    }
   1.995 +
   1.996 +    case eSendingMetadataForRead: {
   1.997 +      MOZ_ASSERT(NS_IsMainThread());
   1.998 +      MOZ_ASSERT(mOpenMode == eOpenForRead);
   1.999 +
  1.1000 +      mState = eWaitingToOpenCacheFileForRead;
  1.1001 +      OnOpenMetadataForRead(mMetadata);
  1.1002 +      return NS_OK;
  1.1003 +    }
  1.1004 +
  1.1005 +    case eReadyToOpenCacheFileForRead: {
  1.1006 +      AssertIsOnIOThread();
  1.1007 +      MOZ_ASSERT(mOpenMode == eOpenForRead);
  1.1008 +
  1.1009 +      rv = OpenCacheFileForRead();
  1.1010 +      if (NS_FAILED(rv)) {
  1.1011 +        Fail();
  1.1012 +        return NS_OK;
  1.1013 +      }
  1.1014 +
  1.1015 +      mState = eSendingCacheFile;
  1.1016 +      NS_DispatchToMainThread(this);
  1.1017 +      return NS_OK;
  1.1018 +    }
  1.1019 +
  1.1020 +    case eSendingCacheFile: {
  1.1021 +      MOZ_ASSERT(NS_IsMainThread());
  1.1022 +
  1.1023 +      mState = eOpened;
  1.1024 +      OnOpenCacheFile();
  1.1025 +      return NS_OK;
  1.1026 +    }
  1.1027 +
  1.1028 +    case eFailing: {
  1.1029 +      MOZ_ASSERT(NS_IsMainThread());
  1.1030 +
  1.1031 +      mState = eFinished;
  1.1032 +      OnFailure();
  1.1033 +      return NS_OK;
  1.1034 +    }
  1.1035 +
  1.1036 +    case eClosing: {
  1.1037 +      MOZ_ASSERT(NS_IsMainThread());
  1.1038 +
  1.1039 +      mState = eFinished;
  1.1040 +      OnClose();
  1.1041 +      return NS_OK;
  1.1042 +    }
  1.1043 +
  1.1044 +    case eWaitingToOpenCacheFileForRead:
  1.1045 +    case eOpened:
  1.1046 +    case eFinished: {
  1.1047 +      MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
  1.1048 +    }
  1.1049 +  }
  1.1050 +
  1.1051 +  MOZ_ASSUME_UNREACHABLE("Corrupt state");
  1.1052 +  return NS_OK;
  1.1053 +}
  1.1054 +
  1.1055 +bool
  1.1056 +FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
  1.1057 +              unsigned* aModuleIndex)
  1.1058 +{
  1.1059 +  // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
  1.1060 +  // also stores an mFastHash of its first sNumFastHashChars so this gives us a
  1.1061 +  // fast way to probabilistically determine whether we have a cache hit. We
  1.1062 +  // still do a full hash of all the chars before returning the cache file to
  1.1063 +  // the engine to avoid penalizing the case where there are multiple cached
  1.1064 +  // asm.js modules where the first sNumFastHashChars are the same. The
  1.1065 +  // mFullHash of each cache entry can have a different mNumChars so the fast
  1.1066 +  // hash allows us to avoid performing up to Metadata::kNumEntries separate
  1.1067 +  // full hashes.
  1.1068 +  uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
  1.1069 +  MOZ_ASSERT(numChars > sNumFastHashChars);
  1.1070 +  uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);
  1.1071 +
  1.1072 +  for (unsigned i = 0; i < Metadata::kNumEntries ; i++) {
  1.1073 +    // Compare the "fast hash" first to see whether it is worthwhile to
  1.1074 +    // hash all the chars.
  1.1075 +    Metadata::Entry entry = aMetadata.mEntries[i];
  1.1076 +    if (entry.mFastHash != fastHash) {
  1.1077 +      continue;
  1.1078 +    }
  1.1079 +
  1.1080 +    // Assuming we have enough characters, hash all the chars it would take
  1.1081 +    // to match this cache entry and compare to the cache entry. If we get a
  1.1082 +    // hit we'll still do a full source match later (in the JS engine), but
  1.1083 +    // the full hash match means this is probably the cache entry we want.
  1.1084 +    if (numChars < entry.mNumChars) {
  1.1085 +      continue;
  1.1086 +    }
  1.1087 +    uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
  1.1088 +    if (entry.mFullHash != fullHash) {
  1.1089 +      continue;
  1.1090 +    }
  1.1091 +
  1.1092 +    *aModuleIndex = entry.mModuleIndex;
  1.1093 +    return true;
  1.1094 +  }
  1.1095 +
  1.1096 +  return false;
  1.1097 +}
  1.1098 +
  1.1099 +// A runnable that executes for a cache access originating in the main process.
  1.1100 +class SingleProcessRunnable MOZ_FINAL : public File,
  1.1101 +                                        private MainProcessRunnable
  1.1102 +{
  1.1103 +public:
  1.1104 +  // In the single-process case, the calling JS compilation thread holds the
  1.1105 +  // nsIPrincipal alive indirectly (via the global object -> compartment ->
  1.1106 +  // principal) so we don't have to ref-count it here. This is fortunate since
  1.1107 +  // we are off the main thread and nsIPrincipals can only be ref-counted on
  1.1108 +  // the main thread.
  1.1109 +  SingleProcessRunnable(nsIPrincipal* aPrincipal,
  1.1110 +                        OpenMode aOpenMode,
  1.1111 +                        WriteParams aWriteParams,
  1.1112 +                        ReadParams aReadParams)
  1.1113 +  : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
  1.1114 +    mReadParams(aReadParams)
  1.1115 +  {
  1.1116 +    MOZ_ASSERT(IsMainProcess());
  1.1117 +    MOZ_ASSERT(!NS_IsMainThread());
  1.1118 +    MOZ_COUNT_CTOR(SingleProcessRunnable);
  1.1119 +  }
  1.1120 +
  1.1121 +  ~SingleProcessRunnable()
  1.1122 +  {
  1.1123 +    MOZ_COUNT_DTOR(SingleProcessRunnable);
  1.1124 +  }
  1.1125 +
  1.1126 +private:
  1.1127 +  void
  1.1128 +  OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1.1129 +  {
  1.1130 +    uint32_t moduleIndex;
  1.1131 +    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
  1.1132 +      MainProcessRunnable::OpenForRead(moduleIndex);
  1.1133 +    } else {
  1.1134 +      MainProcessRunnable::CacheMiss();
  1.1135 +    }
  1.1136 +  }
  1.1137 +
  1.1138 +  void
  1.1139 +  OnOpenCacheFile() MOZ_OVERRIDE
  1.1140 +  {
  1.1141 +    File::OnOpen();
  1.1142 +  }
  1.1143 +
  1.1144 +  void
  1.1145 +  Close() MOZ_OVERRIDE MOZ_FINAL
  1.1146 +  {
  1.1147 +    MainProcessRunnable::Close();
  1.1148 +  }
  1.1149 +
  1.1150 +  void
  1.1151 +  OnFailure() MOZ_OVERRIDE
  1.1152 +  {
  1.1153 +    MainProcessRunnable::OnFailure();
  1.1154 +    File::OnFailure();
  1.1155 +  }
  1.1156 +
  1.1157 +  void
  1.1158 +  OnClose() MOZ_OVERRIDE MOZ_FINAL
  1.1159 +  {
  1.1160 +    MainProcessRunnable::OnClose();
  1.1161 +    File::OnClose();
  1.1162 +  }
  1.1163 +
  1.1164 +  // Avoid MSVC 'dominance' warning by having clear Run() override.
  1.1165 +  NS_IMETHODIMP
  1.1166 +  Run() MOZ_OVERRIDE
  1.1167 +  {
  1.1168 +    return MainProcessRunnable::Run();
  1.1169 +  }
  1.1170 +
  1.1171 +  ReadParams mReadParams;
  1.1172 +};
  1.1173 +
  1.1174 +// A runnable that executes in a parent process for a cache access originating
  1.1175 +// in the content process. This runnable gets registered as an IPDL subprotocol
  1.1176 +// actor so that it can communicate with the corresponding ChildProcessRunnable.
  1.1177 +class ParentProcessRunnable MOZ_FINAL : public PAsmJSCacheEntryParent,
  1.1178 +                                        public MainProcessRunnable
  1.1179 +{
  1.1180 +public:
  1.1181 +  // The given principal comes from an IPC::Principal which will be dec-refed
  1.1182 +  // at the end of the message, so we must ref-count it here. Fortunately, we
  1.1183 +  // are on the main thread (where PContent messages are delivered).
  1.1184 +  ParentProcessRunnable(nsIPrincipal* aPrincipal,
  1.1185 +                        OpenMode aOpenMode,
  1.1186 +                        WriteParams aWriteParams)
  1.1187 +  : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
  1.1188 +    mPrincipalHolder(aPrincipal),
  1.1189 +    mActorDestroyed(false),
  1.1190 +    mOpened(false),
  1.1191 +    mFinished(false)
  1.1192 +  {
  1.1193 +    MOZ_ASSERT(IsMainProcess());
  1.1194 +    MOZ_ASSERT(NS_IsMainThread());
  1.1195 +    MOZ_COUNT_CTOR(ParentProcessRunnable);
  1.1196 +  }
  1.1197 +
  1.1198 +private:
  1.1199 +  ~ParentProcessRunnable()
  1.1200 +  {
  1.1201 +    MOZ_ASSERT(!mPrincipalHolder, "Should have already been released");
  1.1202 +    MOZ_ASSERT(mActorDestroyed);
  1.1203 +    MOZ_ASSERT(mFinished);
  1.1204 +    MOZ_COUNT_DTOR(ParentProcessRunnable);
  1.1205 +  }
  1.1206 +
  1.1207 +  bool
  1.1208 +  Recv__delete__() MOZ_OVERRIDE
  1.1209 +  {
  1.1210 +    MOZ_ASSERT(!mFinished);
  1.1211 +    mFinished = true;
  1.1212 +
  1.1213 +    if (mOpened) {
  1.1214 +      MainProcessRunnable::Close();
  1.1215 +    } else {
  1.1216 +      MainProcessRunnable::Fail();
  1.1217 +    }
  1.1218 +
  1.1219 +    return true;
  1.1220 +  }
  1.1221 +
  1.1222 +  void
  1.1223 +  ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
  1.1224 +  {
  1.1225 +    MOZ_ASSERT(!mActorDestroyed);
  1.1226 +    mActorDestroyed = true;
  1.1227 +
  1.1228 +    // Assume ActorDestroy can happen at any time, so probe the current state to
  1.1229 +    // determine what needs to happen.
  1.1230 +
  1.1231 +    if (mFinished) {
  1.1232 +      return;
  1.1233 +    }
  1.1234 +
  1.1235 +    mFinished = true;
  1.1236 +
  1.1237 +    if (mOpened) {
  1.1238 +      MainProcessRunnable::Close();
  1.1239 +    } else {
  1.1240 +      MainProcessRunnable::Fail();
  1.1241 +    }
  1.1242 +  }
  1.1243 +
  1.1244 +  void
  1.1245 +  OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1.1246 +  {
  1.1247 +    MOZ_ASSERT(NS_IsMainThread());
  1.1248 +
  1.1249 +    if (!SendOnOpenMetadataForRead(aMetadata)) {
  1.1250 +      unused << Send__delete__(this);
  1.1251 +    }
  1.1252 +  }
  1.1253 +
  1.1254 +  bool
  1.1255 +  RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE
  1.1256 +  {
  1.1257 +    MainProcessRunnable::OpenForRead(aModuleIndex);
  1.1258 +    return true;
  1.1259 +  }
  1.1260 +
  1.1261 +  bool
  1.1262 +  RecvCacheMiss() MOZ_OVERRIDE
  1.1263 +  {
  1.1264 +    MainProcessRunnable::CacheMiss();
  1.1265 +    return true;
  1.1266 +  }
  1.1267 +
  1.1268 +  void
  1.1269 +  OnOpenCacheFile() MOZ_OVERRIDE
  1.1270 +  {
  1.1271 +    MOZ_ASSERT(NS_IsMainThread());
  1.1272 +
  1.1273 +    MOZ_ASSERT(!mOpened);
  1.1274 +    mOpened = true;
  1.1275 +
  1.1276 +    FileDescriptor::PlatformHandleType handle =
  1.1277 +      FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc));
  1.1278 +    if (!SendOnOpenCacheFile(mFileSize, handle)) {
  1.1279 +      unused << Send__delete__(this);
  1.1280 +    }
  1.1281 +  }
  1.1282 +
  1.1283 +  void
  1.1284 +  OnClose() MOZ_OVERRIDE MOZ_FINAL
  1.1285 +  {
  1.1286 +    MOZ_ASSERT(NS_IsMainThread());
  1.1287 +    MOZ_ASSERT(mOpened);
  1.1288 +
  1.1289 +    mFinished = true;
  1.1290 +
  1.1291 +    MainProcessRunnable::OnClose();
  1.1292 +
  1.1293 +    MOZ_ASSERT(mActorDestroyed);
  1.1294 +
  1.1295 +    mPrincipalHolder = nullptr;
  1.1296 +  }
  1.1297 +
  1.1298 +  void
  1.1299 +  OnFailure() MOZ_OVERRIDE
  1.1300 +  {
  1.1301 +    MOZ_ASSERT(NS_IsMainThread());
  1.1302 +    MOZ_ASSERT(!mOpened);
  1.1303 +
  1.1304 +    mFinished = true;
  1.1305 +
  1.1306 +    MainProcessRunnable::OnFailure();
  1.1307 +
  1.1308 +    if (!mActorDestroyed) {
  1.1309 +      unused << Send__delete__(this);
  1.1310 +    }
  1.1311 +
  1.1312 +    mPrincipalHolder = nullptr;
  1.1313 +  }
  1.1314 +
  1.1315 +  nsCOMPtr<nsIPrincipal> mPrincipalHolder;
  1.1316 +  bool mActorDestroyed;
  1.1317 +  bool mOpened;
  1.1318 +  bool mFinished;
  1.1319 +};
  1.1320 +
  1.1321 +} // unnamed namespace
  1.1322 +
  1.1323 +PAsmJSCacheEntryParent*
  1.1324 +AllocEntryParent(OpenMode aOpenMode,
  1.1325 +                 WriteParams aWriteParams,
  1.1326 +                 nsIPrincipal* aPrincipal)
  1.1327 +{
  1.1328 +  ParentProcessRunnable* runnable =
  1.1329 +    new ParentProcessRunnable(aPrincipal, aOpenMode, aWriteParams);
  1.1330 +
  1.1331 +  // AddRef to keep the runnable alive until DeallocEntryParent.
  1.1332 +  runnable->AddRef();
  1.1333 +
  1.1334 +  nsresult rv = NS_DispatchToMainThread(runnable);
  1.1335 +  NS_ENSURE_SUCCESS(rv, nullptr);
  1.1336 +
  1.1337 +  return runnable;
  1.1338 +}
  1.1339 +
  1.1340 +void
  1.1341 +DeallocEntryParent(PAsmJSCacheEntryParent* aActor)
  1.1342 +{
  1.1343 +  // Match the AddRef in AllocEntryParent.
  1.1344 +  static_cast<ParentProcessRunnable*>(aActor)->Release();
  1.1345 +}
  1.1346 +
  1.1347 +namespace {
  1.1348 +
  1.1349 +class ChildProcessRunnable MOZ_FINAL : public File,
  1.1350 +                                       public PAsmJSCacheEntryChild
  1.1351 +{
  1.1352 +public:
  1.1353 +  NS_DECL_NSIRUNNABLE
  1.1354 +
  1.1355 +  // In the single-process case, the calling JS compilation thread holds the
  1.1356 +  // nsIPrincipal alive indirectly (via the global object -> compartment ->
  1.1357 +  // principal) so we don't have to ref-count it here. This is fortunate since
  1.1358 +  // we are off the main thread and nsIPrincipals can only be ref-counted on
  1.1359 +  // the main thread.
  1.1360 +  ChildProcessRunnable(nsIPrincipal* aPrincipal,
  1.1361 +                       OpenMode aOpenMode,
  1.1362 +                       WriteParams aWriteParams,
  1.1363 +                       ReadParams aReadParams)
  1.1364 +  : mPrincipal(aPrincipal),
  1.1365 +    mOpenMode(aOpenMode),
  1.1366 +    mWriteParams(aWriteParams),
  1.1367 +    mReadParams(aReadParams),
  1.1368 +    mActorDestroyed(false),
  1.1369 +    mState(eInitial)
  1.1370 +  {
  1.1371 +    MOZ_ASSERT(!IsMainProcess());
  1.1372 +    MOZ_ASSERT(!NS_IsMainThread());
  1.1373 +    MOZ_COUNT_CTOR(ChildProcessRunnable);
  1.1374 +  }
  1.1375 +
  1.1376 +  ~ChildProcessRunnable()
  1.1377 +  {
  1.1378 +    MOZ_ASSERT(mState == eFinished);
  1.1379 +    MOZ_ASSERT(mActorDestroyed);
  1.1380 +    MOZ_COUNT_DTOR(ChildProcessRunnable);
  1.1381 +  }
  1.1382 +
  1.1383 +private:
  1.1384 +  bool
  1.1385 +  RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
  1.1386 +  {
  1.1387 +    MOZ_ASSERT(NS_IsMainThread());
  1.1388 +    MOZ_ASSERT(mState == eOpening);
  1.1389 +
  1.1390 +    uint32_t moduleIndex;
  1.1391 +    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
  1.1392 +      return SendSelectCacheFileToRead(moduleIndex);
  1.1393 +    }
  1.1394 +
  1.1395 +    return SendCacheMiss();
  1.1396 +  }
  1.1397 +
  1.1398 +  bool
  1.1399 +  RecvOnOpenCacheFile(const int64_t& aFileSize,
  1.1400 +                      const FileDescriptor& aFileDesc) MOZ_OVERRIDE
  1.1401 +  {
  1.1402 +    MOZ_ASSERT(NS_IsMainThread());
  1.1403 +    MOZ_ASSERT(mState == eOpening);
  1.1404 +
  1.1405 +    mFileSize = aFileSize;
  1.1406 +
  1.1407 +    mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
  1.1408 +    if (!mFileDesc) {
  1.1409 +      return false;
  1.1410 +    }
  1.1411 +
  1.1412 +    mState = eOpened;
  1.1413 +    File::OnOpen();
  1.1414 +    return true;
  1.1415 +  }
  1.1416 +
  1.1417 +  bool
  1.1418 +  Recv__delete__() MOZ_OVERRIDE
  1.1419 +  {
  1.1420 +    MOZ_ASSERT(NS_IsMainThread());
  1.1421 +    MOZ_ASSERT(mState == eOpening);
  1.1422 +
  1.1423 +    Fail();
  1.1424 +    return true;
  1.1425 +  }
  1.1426 +
  1.1427 +  void
  1.1428 +  ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
  1.1429 +  {
  1.1430 +    MOZ_ASSERT(NS_IsMainThread());
  1.1431 +    mActorDestroyed = true;
  1.1432 +  }
  1.1433 +
  1.1434 +  void
  1.1435 +  Close() MOZ_OVERRIDE MOZ_FINAL
  1.1436 +  {
  1.1437 +    MOZ_ASSERT(mState == eOpened);
  1.1438 +
  1.1439 +    mState = eClosing;
  1.1440 +    NS_DispatchToMainThread(this);
  1.1441 +  }
  1.1442 +
  1.1443 +private:
  1.1444 +  void
  1.1445 +  Fail()
  1.1446 +  {
  1.1447 +    MOZ_ASSERT(NS_IsMainThread());
  1.1448 +    MOZ_ASSERT(mState == eInitial || mState == eOpening);
  1.1449 +
  1.1450 +    mState = eFinished;
  1.1451 +    File::OnFailure();
  1.1452 +  }
  1.1453 +
  1.1454 +  nsIPrincipal* const mPrincipal;
  1.1455 +  const OpenMode mOpenMode;
  1.1456 +  WriteParams mWriteParams;
  1.1457 +  ReadParams mReadParams;
  1.1458 +  bool mActorDestroyed;
  1.1459 +
  1.1460 +  enum State {
  1.1461 +    eInitial, // Just created, waiting to dispatched to the main thread
  1.1462 +    eOpening, // Waiting for the parent process to respond
  1.1463 +    eOpened, // Parent process opened the entry and sent it back
  1.1464 +    eClosing, // Waiting to be dispatched to the main thread to Send__delete__
  1.1465 +    eFinished // Terminal state
  1.1466 +  };
  1.1467 +  State mState;
  1.1468 +};
  1.1469 +
  1.1470 +NS_IMETHODIMP
  1.1471 +ChildProcessRunnable::Run()
  1.1472 +{
  1.1473 +  switch (mState) {
  1.1474 +    case eInitial: {
  1.1475 +      MOZ_ASSERT(NS_IsMainThread());
  1.1476 +
  1.1477 +      // AddRef to keep this runnable alive until IPDL deallocates the
  1.1478 +      // subprotocol (DeallocEntryChild).
  1.1479 +      AddRef();
  1.1480 +
  1.1481 +      if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor(
  1.1482 +        this, mOpenMode, mWriteParams, IPC::Principal(mPrincipal)))
  1.1483 +      {
  1.1484 +        // On failure, undo the AddRef (since DeallocEntryChild will not be
  1.1485 +        // called) and unblock the parsing thread with a failure. The main
  1.1486 +        // thread event loop still holds an outstanding ref which will keep
  1.1487 +        // 'this' alive until returning to the event loop.
  1.1488 +        Release();
  1.1489 +
  1.1490 +        Fail();
  1.1491 +        return NS_OK;
  1.1492 +      }
  1.1493 +
  1.1494 +      mState = eOpening;
  1.1495 +      return NS_OK;
  1.1496 +    }
  1.1497 +
  1.1498 +    case eClosing: {
  1.1499 +      MOZ_ASSERT(NS_IsMainThread());
  1.1500 +
  1.1501 +      // Per FileDescriptorHolder::Finish()'s comment, call before
  1.1502 +      // AllowNextSynchronizedOp (which happens in the parent upon receipt of
  1.1503 +      // the Send__delete__ message).
  1.1504 +      File::OnClose();
  1.1505 +
  1.1506 +      if (!mActorDestroyed) {
  1.1507 +        unused << Send__delete__(this);
  1.1508 +      }
  1.1509 +
  1.1510 +      mState = eFinished;
  1.1511 +      return NS_OK;
  1.1512 +    }
  1.1513 +
  1.1514 +    case eOpening:
  1.1515 +    case eOpened:
  1.1516 +    case eFinished: {
  1.1517 +      MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
  1.1518 +    }
  1.1519 +  }
  1.1520 +
  1.1521 +  MOZ_ASSUME_UNREACHABLE("Corrupt state");
  1.1522 +  return NS_OK;
  1.1523 +}
  1.1524 +
  1.1525 +} // unnamed namespace
  1.1526 +
  1.1527 +void
  1.1528 +DeallocEntryChild(PAsmJSCacheEntryChild* aActor)
  1.1529 +{
  1.1530 +  // Match the AddRef before SendPAsmJSCacheEntryConstructor.
  1.1531 +  static_cast<ChildProcessRunnable*>(aActor)->Release();
  1.1532 +}
  1.1533 +
  1.1534 +namespace {
  1.1535 +
  1.1536 +bool
  1.1537 +OpenFile(nsIPrincipal* aPrincipal,
  1.1538 +         OpenMode aOpenMode,
  1.1539 +         WriteParams aWriteParams,
  1.1540 +         ReadParams aReadParams,
  1.1541 +         File::AutoClose* aFile)
  1.1542 +{
  1.1543 +  MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
  1.1544 +  MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);
  1.1545 +
  1.1546 +  // There are three reasons we don't attempt caching from the main thread:
  1.1547 +  //  1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
  1.1548 +  //     synchronous waiting on the main thread requiring a runnable to be
  1.1549 +  //     dispatched to the main thread.
  1.1550 +  //  2. In the child process: the IPDL PContent messages we need to
  1.1551 +  //     synchronously wait on are dispatched to the main thread.
  1.1552 +  //  3. While a cache lookup *should* be much faster than compilation, IO
  1.1553 +  //     operations can be unpredictably slow and we'd like to avoid the
  1.1554 +  //     occasional janks on the main thread.
  1.1555 +  // We could use a nested event loop to address 1 and 2, but we're potentially
  1.1556 +  // in the middle of running JS (eval()) and nested event loops can be
  1.1557 +  // semantically observable.
  1.1558 +  if (NS_IsMainThread()) {
  1.1559 +    return false;
  1.1560 +  }
  1.1561 +
  1.1562 +  // If we are in a child process, we need to synchronously call into the
  1.1563 +  // parent process to open the file and interact with the QuotaManager. The
  1.1564 +  // child can then map the file into its address space to perform I/O.
  1.1565 +  nsRefPtr<File> file;
  1.1566 +  if (IsMainProcess()) {
  1.1567 +    file = new SingleProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
  1.1568 +                                     aReadParams);
  1.1569 +  } else {
  1.1570 +    file = new ChildProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
  1.1571 +                                    aReadParams);
  1.1572 +  }
  1.1573 +
  1.1574 +  if (!file->BlockUntilOpen(aFile)) {
  1.1575 +    return false;
  1.1576 +  }
  1.1577 +
  1.1578 +  return file->MapMemory(aOpenMode);
  1.1579 +}
  1.1580 +
  1.1581 +} // anonymous namespace
  1.1582 +
  1.1583 +typedef uint32_t AsmJSCookieType;
  1.1584 +static const uint32_t sAsmJSCookie = 0x600d600d;
  1.1585 +
  1.1586 +bool
  1.1587 +OpenEntryForRead(nsIPrincipal* aPrincipal,
  1.1588 +                 const jschar* aBegin,
  1.1589 +                 const jschar* aLimit,
  1.1590 +                 size_t* aSize,
  1.1591 +                 const uint8_t** aMemory,
  1.1592 +                 intptr_t* aFile)
  1.1593 +{
  1.1594 +  if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
  1.1595 +    return false;
  1.1596 +  }
  1.1597 +
  1.1598 +  ReadParams readParams;
  1.1599 +  readParams.mBegin = aBegin;
  1.1600 +  readParams.mLimit = aLimit;
  1.1601 +
  1.1602 +  File::AutoClose file;
  1.1603 +  WriteParams notAWrite;
  1.1604 +  if (!OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &file)) {
  1.1605 +    return false;
  1.1606 +  }
  1.1607 +
  1.1608 +  // Although we trust that the stored cache files have not been arbitrarily
  1.1609 +  // corrupted, it is possible that a previous execution aborted in the middle
  1.1610 +  // of writing a cache file (crash, OOM-killer, etc). To protect against
  1.1611 +  // partially-written cache files, we use the following scheme:
  1.1612 +  //  - Allocate an extra word at the beginning of every cache file which
  1.1613 +  //    starts out 0 (OpenFile opens with PR_TRUNCATE).
  1.1614 +  //  - After the asm.js serialization is complete, PR_SyncMemMap to write
  1.1615 +  //    everything to disk and then store a non-zero value (sAsmJSCookie)
  1.1616 +  //    in the first word.
  1.1617 +  //  - When attempting to read a cache file, check whether the first word is
  1.1618 +  //    sAsmJSCookie.
  1.1619 +  if (file->FileSize() < sizeof(AsmJSCookieType) ||
  1.1620 +      *(AsmJSCookieType*)file->MappedMemory() != sAsmJSCookie) {
  1.1621 +    return false;
  1.1622 +  }
  1.1623 +
  1.1624 +  *aSize = file->FileSize() - sizeof(AsmJSCookieType);
  1.1625 +  *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
  1.1626 +
  1.1627 +  // The caller guarnatees a call to CloseEntryForRead (on success or
  1.1628 +  // failure) at which point the file will be closed.
  1.1629 +  file.Forget(reinterpret_cast<File**>(aFile));
  1.1630 +  return true;
  1.1631 +}
  1.1632 +
  1.1633 +void
  1.1634 +CloseEntryForRead(JS::Handle<JSObject*> global,
  1.1635 +                  size_t aSize,
  1.1636 +                  const uint8_t* aMemory,
  1.1637 +                  intptr_t aFile)
  1.1638 +{
  1.1639 +  File::AutoClose file(reinterpret_cast<File*>(aFile));
  1.1640 +
  1.1641 +  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
  1.1642 +  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
  1.1643 +}
  1.1644 +
  1.1645 +bool
  1.1646 +OpenEntryForWrite(nsIPrincipal* aPrincipal,
  1.1647 +                  bool aInstalled,
  1.1648 +                  const jschar* aBegin,
  1.1649 +                  const jschar* aEnd,
  1.1650 +                  size_t aSize,
  1.1651 +                  uint8_t** aMemory,
  1.1652 +                  intptr_t* aFile)
  1.1653 +{
  1.1654 +  if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
  1.1655 +    return false;
  1.1656 +  }
  1.1657 +
  1.1658 +  // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
  1.1659 +  aSize += sizeof(AsmJSCookieType);
  1.1660 +
  1.1661 +  static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
  1.1662 +
  1.1663 +  WriteParams writeParams;
  1.1664 +  writeParams.mInstalled = aInstalled;
  1.1665 +  writeParams.mSize = aSize;
  1.1666 +  writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
  1.1667 +  writeParams.mNumChars = aEnd - aBegin;
  1.1668 +  writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
  1.1669 +
  1.1670 +  File::AutoClose file;
  1.1671 +  ReadParams notARead;
  1.1672 +  if (!OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file)) {
  1.1673 +    return false;
  1.1674 +  }
  1.1675 +
  1.1676 +  // Strip off the AsmJSCookieType from the buffer returned to the caller,
  1.1677 +  // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
  1.1678 +  // with a cookie.
  1.1679 +  *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
  1.1680 +
  1.1681 +  // The caller guarnatees a call to CloseEntryForWrite (on success or
  1.1682 +  // failure) at which point the file will be closed
  1.1683 +  file.Forget(reinterpret_cast<File**>(aFile));
  1.1684 +  return true;
  1.1685 +}
  1.1686 +
  1.1687 +void
  1.1688 +CloseEntryForWrite(JS::Handle<JSObject*> global,
  1.1689 +                   size_t aSize,
  1.1690 +                   uint8_t* aMemory,
  1.1691 +                   intptr_t aFile)
  1.1692 +{
  1.1693 +  File::AutoClose file(reinterpret_cast<File*>(aFile));
  1.1694 +
  1.1695 +  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
  1.1696 +  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
  1.1697 +
  1.1698 +  // Flush to disk before writing the cookie (see OpenEntryForRead).
  1.1699 +  if (PR_SyncMemMap(file->FileDesc(),
  1.1700 +                    file->MappedMemory(),
  1.1701 +                    file->FileSize()) == PR_SUCCESS) {
  1.1702 +    *(AsmJSCookieType*)file->MappedMemory() = sAsmJSCookie;
  1.1703 +  }
  1.1704 +}
  1.1705 +
  1.1706 +bool
  1.1707 +GetBuildId(JS::BuildIdCharVector* aBuildID)
  1.1708 +{
  1.1709 +  nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
  1.1710 +  if (!info) {
  1.1711 +    return false;
  1.1712 +  }
  1.1713 +
  1.1714 +  nsCString buildID;
  1.1715 +  nsresult rv = info->GetPlatformBuildID(buildID);
  1.1716 +  NS_ENSURE_SUCCESS(rv, false);
  1.1717 +
  1.1718 +  if (!aBuildID->resize(buildID.Length())) {
  1.1719 +    return false;
  1.1720 +  }
  1.1721 +
  1.1722 +  for (size_t i = 0; i < buildID.Length(); i++) {
  1.1723 +    (*aBuildID)[i] = buildID[i];
  1.1724 +  }
  1.1725 +
  1.1726 +  return true;
  1.1727 +}
  1.1728 +
  1.1729 +class Client : public quota::Client
  1.1730 +{
  1.1731 +public:
  1.1732 +  NS_IMETHOD_(MozExternalRefCountType)
  1.1733 +  AddRef() MOZ_OVERRIDE;
  1.1734 +
  1.1735 +  NS_IMETHOD_(MozExternalRefCountType)
  1.1736 +  Release() MOZ_OVERRIDE;
  1.1737 +
  1.1738 +  virtual Type
  1.1739 +  GetType() MOZ_OVERRIDE
  1.1740 +  {
  1.1741 +    return ASMJS;
  1.1742 +  }
  1.1743 +
  1.1744 +  virtual nsresult
  1.1745 +  InitOrigin(PersistenceType aPersistenceType,
  1.1746 +             const nsACString& aGroup,
  1.1747 +             const nsACString& aOrigin,
  1.1748 +             UsageInfo* aUsageInfo) MOZ_OVERRIDE
  1.1749 +  {
  1.1750 +    if (!aUsageInfo) {
  1.1751 +      return NS_OK;
  1.1752 +    }
  1.1753 +    return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
  1.1754 +  }
  1.1755 +
  1.1756 +  virtual nsresult
  1.1757 +  GetUsageForOrigin(PersistenceType aPersistenceType,
  1.1758 +                    const nsACString& aGroup,
  1.1759 +                    const nsACString& aOrigin,
  1.1760 +                    UsageInfo* aUsageInfo) MOZ_OVERRIDE
  1.1761 +  {
  1.1762 +    QuotaManager* qm = QuotaManager::Get();
  1.1763 +    MOZ_ASSERT(qm, "We were being called by the QuotaManager");
  1.1764 +
  1.1765 +    nsCOMPtr<nsIFile> directory;
  1.1766 +    nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
  1.1767 +                                            getter_AddRefs(directory));
  1.1768 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1769 +    MOZ_ASSERT(directory, "We're here because the origin directory exists");
  1.1770 +
  1.1771 +    rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
  1.1772 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1773 +
  1.1774 +    DebugOnly<bool> exists;
  1.1775 +    MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
  1.1776 +
  1.1777 +    nsCOMPtr<nsISimpleEnumerator> entries;
  1.1778 +    rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
  1.1779 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1780 +
  1.1781 +    bool hasMore;
  1.1782 +    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
  1.1783 +           hasMore && !aUsageInfo->Canceled()) {
  1.1784 +      nsCOMPtr<nsISupports> entry;
  1.1785 +      rv = entries->GetNext(getter_AddRefs(entry));
  1.1786 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1787 +
  1.1788 +      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
  1.1789 +      NS_ENSURE_TRUE(file, NS_NOINTERFACE);
  1.1790 +
  1.1791 +      int64_t fileSize;
  1.1792 +      rv = file->GetFileSize(&fileSize);
  1.1793 +      NS_ENSURE_SUCCESS(rv, rv);
  1.1794 +
  1.1795 +      MOZ_ASSERT(fileSize >= 0, "Negative size?!");
  1.1796 +
  1.1797 +      // Since the client is not explicitly storing files, append to database
  1.1798 +      // usage which represents implicit storage allocation.
  1.1799 +      aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
  1.1800 +    }
  1.1801 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1802 +
  1.1803 +    return NS_OK;
  1.1804 +  }
  1.1805 +
  1.1806 +  virtual void
  1.1807 +  OnOriginClearCompleted(PersistenceType aPersistenceType,
  1.1808 +                         const OriginOrPatternString& aOriginOrPattern)
  1.1809 +                         MOZ_OVERRIDE
  1.1810 +  { }
  1.1811 +
  1.1812 +  virtual void
  1.1813 +  ReleaseIOThreadObjects() MOZ_OVERRIDE
  1.1814 +  { }
  1.1815 +
  1.1816 +  virtual bool
  1.1817 +  IsFileServiceUtilized() MOZ_OVERRIDE
  1.1818 +  {
  1.1819 +    return false;
  1.1820 +  }
  1.1821 +
  1.1822 +  virtual bool
  1.1823 +  IsTransactionServiceActivated() MOZ_OVERRIDE
  1.1824 +  {
  1.1825 +    return false;
  1.1826 +  }
  1.1827 +
  1.1828 +  virtual void
  1.1829 +  WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
  1.1830 +                            nsIRunnable* aCallback) MOZ_OVERRIDE
  1.1831 +  {
  1.1832 +    MOZ_ASSUME_UNREACHABLE("There are no storages");
  1.1833 +  }
  1.1834 +
  1.1835 +  virtual void
  1.1836 +  AbortTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
  1.1837 +  {
  1.1838 +    MOZ_ASSUME_UNREACHABLE("There are no storages");
  1.1839 +  }
  1.1840 +
  1.1841 +  virtual bool
  1.1842 +  HasTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
  1.1843 +  {
  1.1844 +    return false;
  1.1845 +  }
  1.1846 +
  1.1847 +  virtual void
  1.1848 +  ShutdownTransactionService() MOZ_OVERRIDE
  1.1849 +  { }
  1.1850 +
  1.1851 +private:
  1.1852 +  nsAutoRefCnt mRefCnt;
  1.1853 +  NS_DECL_OWNINGTHREAD
  1.1854 +};
  1.1855 +
  1.1856 +NS_IMPL_ADDREF(asmjscache::Client)
  1.1857 +NS_IMPL_RELEASE(asmjscache::Client)
  1.1858 +
  1.1859 +quota::Client*
  1.1860 +CreateClient()
  1.1861 +{
  1.1862 +  return new Client();
  1.1863 +}
  1.1864 +
  1.1865 +} // namespace asmjscache
  1.1866 +} // namespace dom
  1.1867 +} // namespace mozilla
  1.1868 +
  1.1869 +namespace IPC {
  1.1870 +
  1.1871 +using mozilla::dom::asmjscache::Metadata;
  1.1872 +using mozilla::dom::asmjscache::WriteParams;
  1.1873 +
  1.1874 +void
  1.1875 +ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam)
  1.1876 +{
  1.1877 +  for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1.1878 +    const Metadata::Entry& entry = aParam.mEntries[i];
  1.1879 +    WriteParam(aMsg, entry.mFastHash);
  1.1880 +    WriteParam(aMsg, entry.mNumChars);
  1.1881 +    WriteParam(aMsg, entry.mFullHash);
  1.1882 +    WriteParam(aMsg, entry.mModuleIndex);
  1.1883 +  }
  1.1884 +}
  1.1885 +
  1.1886 +bool
  1.1887 +ParamTraits<Metadata>::Read(const Message* aMsg, void** aIter,
  1.1888 +                            paramType* aResult)
  1.1889 +{
  1.1890 +  for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1.1891 +    Metadata::Entry& entry = aResult->mEntries[i];
  1.1892 +    if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
  1.1893 +        !ReadParam(aMsg, aIter, &entry.mNumChars) ||
  1.1894 +        !ReadParam(aMsg, aIter, &entry.mFullHash) ||
  1.1895 +        !ReadParam(aMsg, aIter, &entry.mModuleIndex))
  1.1896 +    {
  1.1897 +      return false;
  1.1898 +    }
  1.1899 +  }
  1.1900 +  return true;
  1.1901 +}
  1.1902 +
  1.1903 +void
  1.1904 +ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog)
  1.1905 +{
  1.1906 +  for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
  1.1907 +    const Metadata::Entry& entry = aParam.mEntries[i];
  1.1908 +    LogParam(entry.mFastHash, aLog);
  1.1909 +    LogParam(entry.mNumChars, aLog);
  1.1910 +    LogParam(entry.mFullHash, aLog);
  1.1911 +    LogParam(entry.mModuleIndex, aLog);
  1.1912 +  }
  1.1913 +}
  1.1914 +
  1.1915 +void
  1.1916 +ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
  1.1917 +{
  1.1918 +  WriteParam(aMsg, aParam.mSize);
  1.1919 +  WriteParam(aMsg, aParam.mFastHash);
  1.1920 +  WriteParam(aMsg, aParam.mNumChars);
  1.1921 +  WriteParam(aMsg, aParam.mFullHash);
  1.1922 +  WriteParam(aMsg, aParam.mInstalled);
  1.1923 +}
  1.1924 +
  1.1925 +bool
  1.1926 +ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
  1.1927 +                               paramType* aResult)
  1.1928 +{
  1.1929 +  return ReadParam(aMsg, aIter, &aResult->mSize) &&
  1.1930 +         ReadParam(aMsg, aIter, &aResult->mFastHash) &&
  1.1931 +         ReadParam(aMsg, aIter, &aResult->mNumChars) &&
  1.1932 +         ReadParam(aMsg, aIter, &aResult->mFullHash) &&
  1.1933 +         ReadParam(aMsg, aIter, &aResult->mInstalled);
  1.1934 +}
  1.1935 +
  1.1936 +void
  1.1937 +ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
  1.1938 +{
  1.1939 +  LogParam(aParam.mSize, aLog);
  1.1940 +  LogParam(aParam.mFastHash, aLog);
  1.1941 +  LogParam(aParam.mNumChars, aLog);
  1.1942 +  LogParam(aParam.mFullHash, aLog);
  1.1943 +  LogParam(aParam.mInstalled, aLog);
  1.1944 +}
  1.1945 +
  1.1946 +} // namespace IPC

mercurial