tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ScriptPreloader.cpp (46924B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ScriptPreloader-inl.h"
      8 #include "mozilla/AlreadyAddRefed.h"
      9 #include "mozilla/Monitor.h"
     10 
     11 #include "mozilla/ScriptPreloader.h"
     12 #include "mozilla/loader/ScriptCacheActors.h"
     13 
     14 #include "mozilla/URLPreloader.h"
     15 
     16 #include "mozilla/Components.h"
     17 #include "mozilla/DebugOnly.h"
     18 #include "mozilla/FileUtils.h"
     19 #include "mozilla/IOBuffers.h"
     20 #include "mozilla/Logging.h"
     21 #include "mozilla/ScopeExit.h"
     22 #include "mozilla/Services.h"
     23 #include "mozilla/StaticPrefs_javascript.h"
     24 #include "mozilla/TaskController.h"
     25 #include "mozilla/glean/JsXpconnectMetrics.h"
     26 #include "mozilla/glean/XpcomMetrics.h"
     27 #include "mozilla/Try.h"
     28 #include "mozilla/dom/ContentChild.h"
     29 #include "mozilla/dom/ContentParent.h"
     30 #include "mozilla/dom/Document.h"
     31 #include "mozilla/scache/StartupCache.h"
     32 
     33 #include "crc32c.h"
     34 #include "js/CompileOptions.h"              // JS::ReadOnlyCompileOptions
     35 #include "js/experimental/JSStencil.h"      // JS::Stencil, JS::DecodeStencil
     36 #include "js/experimental/CompileScript.h"  // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize
     37 #include "js/Transcoding.h"
     38 #include "MainThreadUtils.h"
     39 #include "nsDebug.h"
     40 #include "nsDirectoryServiceUtils.h"
     41 #include "nsIFile.h"
     42 #include "nsIObserverService.h"
     43 #include "nsJSUtils.h"
     44 #include "nsMemoryReporterManager.h"
     45 #include "nsNetUtil.h"
     46 #include "nsProxyRelease.h"
     47 #include "nsThreadUtils.h"
     48 #include "nsXULAppAPI.h"
     49 #include "xpcpublic.h"
     50 
     51 #define STARTUP_COMPLETE_TOPIC "browser-delayed-startup-finished"
     52 #define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
     53 #define CONTENT_DOCUMENT_LOADED_TOPIC "content-document-loaded"
     54 #define CACHE_WRITE_TOPIC "browser-idle-startup-tasks-finished"
     55 #define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"
     56 #define CACHE_INVALIDATE_TOPIC "startupcache-invalidate"
     57 
     58 // The maximum time we'll wait for a child process to finish starting up before
     59 // we send its script data back to the parent.
     60 constexpr uint32_t CHILD_STARTUP_TIMEOUT_MS = 8000;
     61 
     62 namespace mozilla {
     63 namespace {
     64 static LazyLogModule gLog("ScriptPreloader");
     65 
     66 #define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
     67 }  // namespace
     68 
     69 using mozilla::dom::AutoJSAPI;
     70 using mozilla::dom::ContentChild;
     71 using mozilla::dom::ContentParent;
     72 using namespace mozilla::loader;
     73 using mozilla::scache::StartupCache;
     74 
     75 using namespace JS;
     76 
     77 ProcessType ScriptPreloader::sProcessType;
     78 
     79 nsresult ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
     80                                         nsISupports* aData, bool aAnonymize) {
     81  MOZ_COLLECT_REPORT(
     82      "explicit/script-preloader/heap/saved-scripts", KIND_HEAP, UNITS_BYTES,
     83      SizeOfHashEntries<ScriptStatus::Saved>(mScripts, MallocSizeOf),
     84      "Memory used to hold the scripts which have been executed in this "
     85      "session, and will be written to the startup script cache file.");
     86 
     87  MOZ_COLLECT_REPORT(
     88      "explicit/script-preloader/heap/restored-scripts", KIND_HEAP, UNITS_BYTES,
     89      SizeOfHashEntries<ScriptStatus::Restored>(mScripts, MallocSizeOf),
     90      "Memory used to hold the scripts which have been restored from the "
     91      "startup script cache file, but have not been executed in this session.");
     92 
     93  MOZ_COLLECT_REPORT("explicit/script-preloader/heap/other", KIND_HEAP,
     94                     UNITS_BYTES, ShallowHeapSizeOfIncludingThis(MallocSizeOf),
     95                     "Memory used by the script cache service itself.");
     96 
     97  // Since the mem-mapped cache file is mapped into memory, we want to report
     98  // it as explicit memory somewhere. But since the child cache is shared
     99  // between all processes, we don't want to report it as explicit memory for
    100  // all of them. So we report it as explicit only in the parent process, and
    101  // non-explicit everywhere else.
    102  if (XRE_IsParentProcess()) {
    103    MOZ_COLLECT_REPORT("explicit/script-preloader/non-heap/memmapped-cache",
    104                       KIND_NONHEAP, UNITS_BYTES,
    105                       mCacheData->nonHeapSizeOfExcludingThis(),
    106                       "The memory-mapped startup script cache file.");
    107  } else {
    108    MOZ_COLLECT_REPORT("script-preloader-memmapped-cache", KIND_NONHEAP,
    109                       UNITS_BYTES, mCacheData->nonHeapSizeOfExcludingThis(),
    110                       "The memory-mapped startup script cache file.");
    111  }
    112 
    113  return NS_OK;
    114 }
    115 
    116 StaticRefPtr<ScriptPreloader> ScriptPreloader::gScriptPreloader;
    117 StaticRefPtr<ScriptPreloader> ScriptPreloader::gChildScriptPreloader;
    118 StaticAutoPtr<AutoMemMap> ScriptPreloader::gCacheData;
    119 StaticAutoPtr<AutoMemMap> ScriptPreloader::gChildCacheData;
    120 
    121 ScriptPreloader& ScriptPreloader::GetSingleton() {
    122  if (!gScriptPreloader) {
    123    AssertIsOnMainThread();
    124    if (XRE_IsParentProcess()) {
    125      gCacheData = new AutoMemMap();
    126      gScriptPreloader = new ScriptPreloader(gCacheData.get());
    127      gScriptPreloader->mChildCache = &GetChildSingleton();
    128      (void)gScriptPreloader->InitCache();
    129    } else {
    130      gScriptPreloader = &GetChildSingleton();
    131    }
    132  }
    133 
    134  return *gScriptPreloader;
    135 }
    136 
    137 // The child singleton is available in all processes, including the parent, and
    138 // is used for scripts which are expected to be loaded into child processes
    139 // (such as process and frame scripts), or scripts that have already been loaded
    140 // into a child. The child caches are managed as follows:
    141 //
    142 // - Every startup, we open the cache file from the last session, move it to a
    143 //  new location, and begin pre-loading the scripts that are stored in it. There
    144 //  is a separate cache file for parent and content processes, but the parent
    145 //  process opens both the parent and content cache files.
    146 //
    147 // - Once startup is complete, we write a new cache file for the next session,
    148 //   containing only the scripts that were used during early startup, so we
    149 //   don't waste pre-loading scripts that may not be needed.
    150 //
    151 // - For content processes, opening and writing the cache file is handled in the
    152 //  parent process. The first content process of each type sends back the data
    153 //  for scripts that were loaded in early startup, and the parent merges them
    154 //  and writes them to a cache file.
    155 //
    156 // - Currently, content processes only benefit from the cache data written
    157 //  during the *previous* session. Ideally, new content processes should
    158 //  probably use the cache data written during this session if there was no
    159 //  previous cache file, but I'd rather do that as a follow-up.
    160 ScriptPreloader& ScriptPreloader::GetChildSingleton() {
    161  if (!gChildScriptPreloader) {
    162    AssertIsOnMainThread();
    163    gChildCacheData = new AutoMemMap();
    164    gChildScriptPreloader = new ScriptPreloader(gChildCacheData.get());
    165    if (XRE_IsParentProcess()) {
    166      (void)gChildScriptPreloader->InitCache(u"scriptCache-child"_ns);
    167    }
    168  }
    169 
    170  return *gChildScriptPreloader;
    171 }
    172 
    173 /* static */
    174 void ScriptPreloader::DeleteSingleton() {
    175  gScriptPreloader = nullptr;
    176  gChildScriptPreloader = nullptr;
    177 }
    178 
    179 /* static */
    180 void ScriptPreloader::DeleteCacheDataSingleton() {
    181  MOZ_ASSERT(!gScriptPreloader);
    182  MOZ_ASSERT(!gChildScriptPreloader);
    183 
    184  gCacheData = nullptr;
    185  gChildCacheData = nullptr;
    186 }
    187 
    188 void ScriptPreloader::InitContentChild(ContentParent& parent) {
    189  AssertIsOnMainThread();
    190 
    191  auto& cache = GetChildSingleton();
    192  cache.mSaveMonitor.NoteOnMainThread();
    193 
    194  // We want startup script data from the first process of a given type.
    195  // That process sends back its script data before it executes any
    196  // untrusted code, and then we never accept further script data for that
    197  // type of process for the rest of the session.
    198  //
    199  // The script data from each process type is merged with the data from the
    200  // parent process's frame and process scripts, and shared between all
    201  // content process types in the next session.
    202  //
    203  // Note that if the first process of a given type crashes or shuts down
    204  // before sending us its script data, we silently ignore it, and data for
    205  // that process type is not included in the next session's cache. This
    206  // should be a sufficiently rare occurrence that it's not worth trying to
    207  // handle specially.
    208  auto processType = GetChildProcessType(parent.GetRemoteType());
    209  bool wantScriptData = !cache.mInitializedProcesses.contains(processType);
    210  cache.mInitializedProcesses += processType;
    211 
    212  auto fd = cache.mCacheData->cloneFileDescriptor();
    213  // Don't send original cache data to new processes if the cache has been
    214  // invalidated.
    215  if (fd.IsValid() && !cache.mCacheInvalidated) {
    216    (void)parent.SendPScriptCacheConstructor(fd, wantScriptData);
    217  } else {
    218    (void)parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND,
    219                                             wantScriptData);
    220  }
    221 }
    222 
    223 ProcessType ScriptPreloader::GetChildProcessType(const nsACString& remoteType) {
    224  if (remoteType == EXTENSION_REMOTE_TYPE) {
    225    return ProcessType::Extension;
    226  }
    227  if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
    228    return ProcessType::PrivilegedAbout;
    229  }
    230  return ProcessType::Web;
    231 }
    232 
    233 ScriptPreloader::ScriptPreloader(AutoMemMap* cacheData)
    234    : mCacheData(cacheData),
    235      mMonitor("[ScriptPreloader.mMonitor]"),
    236      mSaveMonitor("[ScriptPreloader.mSaveMonitor]") {
    237  // We do not set the process type for child processes here because the
    238  // remoteType in ContentChild is not ready yet.
    239  if (XRE_IsParentProcess()) {
    240    sProcessType = ProcessType::Parent;
    241  }
    242 
    243  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    244  MOZ_RELEASE_ASSERT(obs);
    245 
    246  if (XRE_IsParentProcess()) {
    247    // In the parent process, we want to freeze the script cache as soon
    248    // as idle tasks for the first browser window have completed.
    249    obs->AddObserver(this, STARTUP_COMPLETE_TOPIC, false);
    250    obs->AddObserver(this, CACHE_WRITE_TOPIC, false);
    251  }
    252 
    253  obs->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
    254  obs->AddObserver(this, CACHE_INVALIDATE_TOPIC, false);
    255 }
    256 
    257 ScriptPreloader::~ScriptPreloader() { Cleanup(); }
    258 
    259 void ScriptPreloader::Cleanup() {
    260  mScripts.Clear();
    261  UnregisterWeakMemoryReporter(this);
    262 }
    263 
    264 void ScriptPreloader::StartCacheWrite() {
    265  MOZ_DIAGNOSTIC_ASSERT(!mSaveThread);
    266 
    267  (void)NS_NewNamedThread("SaveScripts", getter_AddRefs(mSaveThread), this);
    268 
    269  nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
    270  barrier->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
    271                      u""_ns);
    272 }
    273 
    274 void ScriptPreloader::InvalidateCache() {
    275  {
    276    mMonitor.AssertNotCurrentThreadOwns();
    277    MonitorAutoLock mal(mMonitor);
    278 
    279    // Wait for pending off-thread parses to finish, since they depend on the
    280    // memory allocated by our CachedStencil, and can't be canceled
    281    // asynchronously.
    282    FinishPendingParses(mal);
    283 
    284    // Pending scripts should have been cleared by the above, and the queue
    285    // should have been reset.
    286    MOZ_ASSERT(mDecodingScripts.isEmpty());
    287    MOZ_ASSERT(!mDecodedStencils);
    288 
    289    mScripts.Clear();
    290 
    291    // If we've already finished saving the cache at this point, start a new
    292    // delayed save operation. This will write out an empty cache file in place
    293    // of any cache file we've already written out this session, which will
    294    // prevent us from falling back to the current session's cache file on the
    295    // next startup.
    296    if (mSaveComplete && !mSaveThread && mChildCache) {
    297      mSaveComplete = false;
    298 
    299      StartCacheWrite();
    300    }
    301  }
    302 
    303  {
    304    MonitorAutoLock saveMonitorAutoLock(mSaveMonitor.Lock());
    305    mSaveMonitor.NoteExclusiveAccess();
    306 
    307    mCacheInvalidated = true;
    308  }
    309 
    310  // If we're waiting on a timeout to finish saving, interrupt it and just save
    311  // immediately.
    312  mSaveMonitor.Lock().NotifyAll();
    313 }
    314 
    315 nsresult ScriptPreloader::Observe(nsISupports* subject, const char* topic,
    316                                  const char16_t* data) {
    317  AssertIsOnMainThread();
    318 
    319  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    320  if (!strcmp(topic, STARTUP_COMPLETE_TOPIC)) {
    321    obs->RemoveObserver(this, STARTUP_COMPLETE_TOPIC);
    322 
    323    MOZ_ASSERT(XRE_IsParentProcess());
    324 
    325    mStartupFinished = true;
    326    URLPreloader::GetSingleton().SetStartupFinished();
    327  } else if (!strcmp(topic, CACHE_WRITE_TOPIC)) {
    328    obs->RemoveObserver(this, CACHE_WRITE_TOPIC);
    329 
    330    MOZ_ASSERT(mStartupFinished);
    331    MOZ_ASSERT(XRE_IsParentProcess());
    332 
    333    if (mChildCache && !mSaveComplete && !mSaveThread) {
    334      StartCacheWrite();
    335    }
    336  } else if (mContentStartupFinishedTopic.Equals(topic)) {
    337    // If this is an uninitialized about:blank viewer or a chrome: document
    338    // (which should always be an XBL binding document), ignore it. We don't
    339    // have to worry about it loading malicious content.
    340    if (nsCOMPtr<dom::Document> doc = do_QueryInterface(subject)) {
    341      nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
    342 
    343      if ((NS_IsAboutBlank(uri) &&
    344           doc->GetReadyStateEnum() == doc->READYSTATE_UNINITIALIZED) ||
    345          uri->SchemeIs("chrome")) {
    346        return NS_OK;
    347      }
    348    }
    349    FinishContentStartup();
    350  } else if (!strcmp(topic, "timer-callback")) {
    351    FinishContentStartup();
    352  } else if (!strcmp(topic, XPCOM_SHUTDOWN_TOPIC)) {
    353    // Wait for any pending parses to finish at this point, to avoid creating
    354    // new stencils during destroying the JS runtime.
    355    MonitorAutoLock mal(mMonitor);
    356    FinishPendingParses(mal);
    357  } else if (!strcmp(topic, CACHE_INVALIDATE_TOPIC)) {
    358    InvalidateCache();
    359  }
    360 
    361  return NS_OK;
    362 }
    363 
    364 void ScriptPreloader::FinishContentStartup() {
    365  MOZ_ASSERT(XRE_IsContentProcess());
    366 
    367 #ifdef DEBUG
    368  if (mContentStartupFinishedTopic.Equals(CONTENT_DOCUMENT_LOADED_TOPIC)) {
    369    MOZ_ASSERT(sProcessType == ProcessType::PrivilegedAbout);
    370  } else {
    371    MOZ_ASSERT(sProcessType != ProcessType::PrivilegedAbout);
    372  }
    373 #endif /* DEBUG */
    374 
    375  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    376  obs->RemoveObserver(this, mContentStartupFinishedTopic.get());
    377 
    378  mSaveTimer = nullptr;
    379 
    380  mStartupFinished = true;
    381 
    382  if (mChildActor) {
    383    mChildActor->SendScriptsAndFinalize(mScripts);
    384  }
    385 
    386 #ifdef XP_WIN
    387  // Record the amount of USS at startup. This is Windows-only for now,
    388  // we could turn it on for Linux relatively cheaply. On macOS it can have
    389  // a perf impact. Only record this for non-privileged processes because
    390  // privileged processes record this value at a different time, leading to
    391  // a higher value which skews the telemetry.
    392  if (sProcessType != ProcessType::PrivilegedAbout) {
    393    mozilla::glean::memory::unique_content_startup.Accumulate(
    394        nsMemoryReporterManager::ResidentUnique() / 1024);
    395  }
    396 #endif
    397 }
    398 
    399 bool ScriptPreloader::WillWriteScripts() {
    400  return !mDataPrepared && (XRE_IsParentProcess() || mChildActor);
    401 }
    402 
    403 Result<nsCOMPtr<nsIFile>, nsresult> ScriptPreloader::GetCacheFile(
    404    const nsAString& suffix) {
    405  NS_ENSURE_TRUE(mProfD, Err(NS_ERROR_NOT_INITIALIZED));
    406 
    407  nsCOMPtr<nsIFile> cacheFile;
    408  MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
    409 
    410  MOZ_TRY(cacheFile->AppendNative("startupCache"_ns));
    411  (void)cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
    412 
    413  MOZ_TRY(cacheFile->Append(mBaseName + suffix));
    414 
    415  return std::move(cacheFile);
    416 }
    417 
    418 static const uint8_t MAGIC[] = "mozXDRcachev003";
    419 
    420 Result<Ok, nsresult> ScriptPreloader::OpenCache() {
    421  if (StartupCache::GetIgnoreDiskCache()) {
    422    return Err(NS_ERROR_ABORT);
    423  }
    424 
    425  MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
    426 
    427  nsCOMPtr<nsIFile> cacheFile = MOZ_TRY(GetCacheFile(u".bin"_ns));
    428 
    429  bool exists;
    430  MOZ_TRY(cacheFile->Exists(&exists));
    431  if (exists) {
    432    MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + u"-current.bin"_ns));
    433  } else {
    434    MOZ_TRY(cacheFile->SetLeafName(mBaseName + u"-current.bin"_ns));
    435    MOZ_TRY(cacheFile->Exists(&exists));
    436    if (!exists) {
    437      return Err(NS_ERROR_FILE_NOT_FOUND);
    438    }
    439  }
    440 
    441  MOZ_TRY(mCacheData->init(cacheFile));
    442 
    443  return Ok();
    444 }
    445 
    446 // Opens the script cache file for this session, and initializes the script
    447 // cache based on its contents. See WriteCache for details of the cache file.
    448 Result<Ok, nsresult> ScriptPreloader::InitCache(const nsAString& basePath) {
    449  mCacheInitialized = true;
    450  mBaseName = basePath;
    451 
    452  RegisterWeakMemoryReporter(this);
    453 
    454  if (!XRE_IsParentProcess()) {
    455    return Ok();
    456  }
    457 
    458  // Grab the compilation scope before initializing the URLPreloader, since
    459  // it's not safe to run component loader code during its critical section.
    460  AutoSafeJSAPI jsapi;
    461  JS::RootedObject scope(jsapi.cx(), xpc::CompilationScope());
    462 
    463  // Note: Code on the main thread *must not access Omnijar in any way* until
    464  // this AutoBeginReading guard is destroyed.
    465  URLPreloader::AutoBeginReading abr;
    466 
    467  MOZ_TRY(OpenCache());
    468 
    469  return InitCacheInternal(scope);
    470 }
    471 
    472 Result<Ok, nsresult> ScriptPreloader::InitCache(
    473    const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild) {
    474  MOZ_ASSERT(XRE_IsContentProcess());
    475 
    476  mCacheInitialized = true;
    477  mChildActor = cacheChild;
    478  sProcessType =
    479      GetChildProcessType(dom::ContentChild::GetSingleton()->GetRemoteType());
    480 
    481  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    482  MOZ_RELEASE_ASSERT(obs);
    483 
    484  if (sProcessType == ProcessType::PrivilegedAbout) {
    485    // Since we control all of the documents loaded in the privileged
    486    // content process, we can increase the window of active time for the
    487    // ScriptPreloader to include the scripts that are loaded until the
    488    // first document finishes loading.
    489    mContentStartupFinishedTopic.AssignLiteral(CONTENT_DOCUMENT_LOADED_TOPIC);
    490  } else {
    491    // In the child process, we need to freeze the script cache before any
    492    // untrusted code has been executed. The insertion of the first DOM
    493    // document element may sometimes be earlier than is ideal, but at
    494    // least it should always be safe.
    495    mContentStartupFinishedTopic.AssignLiteral(DOC_ELEM_INSERTED_TOPIC);
    496  }
    497  obs->AddObserver(this, mContentStartupFinishedTopic.get(), false);
    498 
    499  RegisterWeakMemoryReporter(this);
    500 
    501  auto cleanup = MakeScopeExit([&] {
    502    // If the parent is expecting cache data from us, make sure we send it
    503    // before it writes out its cache file. For normal proceses, this isn't
    504    // a concern, since they begin loading documents quite early. For the
    505    // preloaded process, we may end up waiting a long time (or, indeed,
    506    // never loading a document), so we need an additional timeout.
    507    if (cacheChild) {
    508      NS_NewTimerWithObserver(getter_AddRefs(mSaveTimer), this,
    509                              CHILD_STARTUP_TIMEOUT_MS,
    510                              nsITimer::TYPE_ONE_SHOT);
    511    }
    512  });
    513 
    514  if (cacheFile.isNothing()) {
    515    return Ok();
    516  }
    517 
    518  MOZ_TRY(mCacheData->init(cacheFile.ref()));
    519 
    520  return InitCacheInternal();
    521 }
    522 
    523 Result<Ok, nsresult> ScriptPreloader::InitCacheInternal(
    524    JS::HandleObject scope) {
    525  auto size = mCacheData->size();
    526 
    527  uint32_t headerSize;
    528  uint32_t crc;
    529  if (size < sizeof(MAGIC) + sizeof(headerSize) + sizeof(crc)) {
    530    return Err(NS_ERROR_UNEXPECTED);
    531  }
    532 
    533  auto data = mCacheData->get<uint8_t>();
    534  MOZ_RELEASE_ASSERT(JS::IsTranscodingBytecodeAligned(data.get()));
    535 
    536  auto end = data + size;
    537 
    538  if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) {
    539    return Err(NS_ERROR_UNEXPECTED);
    540  }
    541  data += sizeof(MAGIC);
    542 
    543  headerSize = LittleEndian::readUint32(data.get());
    544  data += sizeof(headerSize);
    545 
    546  crc = LittleEndian::readUint32(data.get());
    547  data += sizeof(crc);
    548 
    549  if (data + headerSize > end) {
    550    return Err(NS_ERROR_UNEXPECTED);
    551  }
    552 
    553  if (crc != ComputeCrc32c(~0, data.get(), headerSize)) {
    554    return Err(NS_ERROR_UNEXPECTED);
    555  }
    556 
    557  {
    558    auto cleanup = MakeScopeExit([&]() { mScripts.Clear(); });
    559 
    560    LinkedList<CachedStencil> scripts;
    561 
    562    Range<const uint8_t> header(data, data + headerSize);
    563    data += headerSize;
    564 
    565    // Reconstruct alignment padding if required.
    566    size_t currentOffset = data - mCacheData->get<uint8_t>();
    567    data += JS::AlignTranscodingBytecodeOffset(currentOffset) - currentOffset;
    568 
    569    InputBuffer buf(header);
    570 
    571    size_t offset = 0;
    572    while (!buf.finished()) {
    573      auto script = MakeUnique<CachedStencil>(*this, buf);
    574      MOZ_RELEASE_ASSERT(script);
    575 
    576      auto scriptData = data + script->mOffset;
    577      if (!JS::IsTranscodingBytecodeAligned(scriptData.get())) {
    578        return Err(NS_ERROR_UNEXPECTED);
    579      }
    580 
    581      if (scriptData + script->mSize > end) {
    582        return Err(NS_ERROR_UNEXPECTED);
    583      }
    584 
    585      // Make sure offsets match what we'd expect based on script ordering and
    586      // size, as a basic sanity check.
    587      if (script->mOffset != offset) {
    588        return Err(NS_ERROR_UNEXPECTED);
    589      }
    590      offset += script->mSize;
    591 
    592      script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
    593 
    594      // Don't pre-decode the script unless it was used in this process type
    595      // during the previous session.
    596      if (script->mOriginalProcessTypes.contains(CurrentProcessType())) {
    597        scripts.insertBack(script.get());
    598      } else {
    599        script->mReadyToExecute = true;
    600      }
    601 
    602      const auto& cachePath = script->mCachePath;
    603      mScripts.InsertOrUpdate(cachePath, std::move(script));
    604    }
    605 
    606    if (buf.error()) {
    607      return Err(NS_ERROR_UNEXPECTED);
    608    }
    609 
    610    mDecodingScripts = std::move(scripts);
    611    cleanup.release();
    612  }
    613 
    614  StartDecodeTask(scope);
    615  return Ok();
    616 }
    617 
    618 void ScriptPreloader::PrepareCacheWriteInternal() {
    619  MOZ_ASSERT(NS_IsMainThread());
    620 
    621  mMonitor.AssertCurrentThreadOwns();
    622 
    623  auto cleanup = MakeScopeExit([&]() {
    624    if (mChildCache) {
    625      mChildCache->PrepareCacheWrite();
    626    }
    627  });
    628 
    629  if (mDataPrepared) {
    630    return;
    631  }
    632 
    633  JS::FrontendContext* fc = JS::NewFrontendContext();
    634  if (!fc) {
    635    return;
    636  }
    637 
    638  bool found = false;
    639  for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
    640    // Don't write any scripts that are also in the child cache. They'll be
    641    // loaded from the child cache in that case, so there's no need to write
    642    // them twice.
    643    CachedStencil* childScript =
    644        mChildCache ? mChildCache->mScripts.Get(script->mCachePath) : nullptr;
    645    if (childScript && !childScript->mProcessTypes.isEmpty()) {
    646      childScript->UpdateLoadTime(script->mLoadTime);
    647      childScript->mProcessTypes += script->mProcessTypes;
    648      script.Remove();
    649      continue;
    650    }
    651 
    652    if (!(script->mProcessTypes == script->mOriginalProcessTypes)) {
    653      // Note: EnumSet doesn't support operator!=, hence the weird form above.
    654      found = true;
    655    }
    656 
    657    if (!script->mSize && !script->XDREncode(fc)) {
    658      script.Remove();
    659    }
    660  }
    661 
    662  JS::DestroyFrontendContext(fc);
    663 
    664  if (!found) {
    665    mSaveComplete = true;
    666    return;
    667  }
    668 
    669  mDataPrepared = true;
    670 }
    671 
    672 void ScriptPreloader::PrepareCacheWrite() {
    673  MonitorAutoLock mal(mMonitor);
    674 
    675  PrepareCacheWriteInternal();
    676 }
    677 
    678 // A struct to hold reference to a CachedStencil and the snapshot of the
    679 // CachedStencil::mLoadTime field.
    680 // CachedStencil::mLoadTime field can be modified concurrently, and we need
    681 // to create a snapshot, in order to sort scripts.
    682 struct CachedStencilRefAndTime {
    683  using CachedStencil = ScriptPreloader::CachedStencil;
    684  CachedStencil* mStencil;
    685  TimeStamp mLoadTime;
    686 
    687  explicit CachedStencilRefAndTime(CachedStencil* aStencil)
    688      : mStencil(aStencil), mLoadTime(aStencil->mLoadTime) {}
    689 
    690  // For use with nsTArray::Sort.
    691  //
    692  // Orders scripts by script load time, so that scripts which are needed
    693  // earlier are stored earlier, and scripts needed at approximately the
    694  // same time are stored approximately contiguously.
    695  struct Comparator {
    696    bool Equals(const CachedStencilRefAndTime& a,
    697                const CachedStencilRefAndTime& b) const {
    698      return a.mLoadTime == b.mLoadTime;
    699    }
    700 
    701    bool LessThan(const CachedStencilRefAndTime& a,
    702                  const CachedStencilRefAndTime& b) const {
    703      return a.mLoadTime < b.mLoadTime;
    704    }
    705  };
    706 } JS_HAZ_NON_GC_POINTER;
    707 
    708 // Writes out a script cache file for the scripts accessed during early
    709 // startup in this session. The cache file is a little-endian binary file with
    710 // the following format:
    711 //
    712 // - A uint32 containing the size of the header block.
    713 //
    714 // - A header entry for each file stored in the cache containing:
    715 //   - The URL that the script was originally read from.
    716 //   - Its cache key.
    717 //   - The offset of its XDR data within the XDR data block.
    718 //   - The size of its XDR data in the XDR data block.
    719 //   - A bit field describing which process types the script is used in.
    720 //
    721 // - A block of XDR data for the encoded scripts, with each script's data at
    722 //   an offset from the start of the block, as specified above.
    723 Result<Ok, nsresult> ScriptPreloader::WriteCache() {
    724  MOZ_ASSERT(!NS_IsMainThread());
    725 
    726  if (!mDataPrepared && !mSaveComplete) {
    727    MonitorAutoUnlock mau(mSaveMonitor.Lock());
    728 
    729    NS_DispatchAndSpinEventLoopUntilComplete(
    730        "ScriptPreloader::PrepareCacheWrite"_ns,
    731        GetMainThreadSerialEventTarget(),
    732        NewRunnableMethod("ScriptPreloader::PrepareCacheWrite", this,
    733                          &ScriptPreloader::PrepareCacheWrite));
    734  }
    735 
    736  if (mSaveComplete) {
    737    // If we don't have anything we need to save, we're done.
    738    return Ok();
    739  }
    740 
    741  nsCOMPtr<nsIFile> cacheFile = MOZ_TRY(GetCacheFile(u"-new.bin"_ns));
    742 
    743  bool exists;
    744  MOZ_TRY(cacheFile->Exists(&exists));
    745  if (exists) {
    746    MOZ_TRY(cacheFile->Remove(false));
    747  }
    748 
    749  {
    750    AutoFDClose raiiFd;
    751    MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644,
    752                                        getter_Transfers(raiiFd)));
    753    const auto fd = raiiFd.get();
    754 
    755    // We also need to hold mMonitor while we're touching scripts in
    756    // mScripts, or they may be freed before we're done with them.
    757    mMonitor.AssertNotCurrentThreadOwns();
    758    MonitorAutoLock mal(mMonitor);
    759 
    760    nsTArray<CachedStencilRefAndTime> scriptRefs;
    761    for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
    762      scriptRefs.AppendElement(CachedStencilRefAndTime(script));
    763    }
    764 
    765    // Sort scripts by load time, with async loaded scripts before sync scripts.
    766    // Since async scripts are always loaded immediately at startup, it helps to
    767    // have them stored contiguously.
    768    scriptRefs.Sort(CachedStencilRefAndTime::Comparator());
    769 
    770    OutputBuffer buf;
    771    size_t offset = 0;
    772    for (auto& scriptRef : scriptRefs) {
    773      auto* script = scriptRef.mStencil;
    774      script->mOffset = offset;
    775      MOZ_DIAGNOSTIC_ASSERT(
    776          JS::IsTranscodingBytecodeOffsetAligned(script->mOffset));
    777      script->Code(buf);
    778 
    779      offset += script->mSize;
    780      MOZ_DIAGNOSTIC_ASSERT(
    781          JS::IsTranscodingBytecodeOffsetAligned(script->mSize));
    782    }
    783 
    784    uint8_t headerSize[4];
    785    LittleEndian::writeUint32(headerSize, buf.cursor());
    786 
    787    uint8_t crc[4];
    788    LittleEndian::writeUint32(crc, ComputeCrc32c(~0, buf.Get(), buf.cursor()));
    789 
    790    MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
    791    MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
    792    MOZ_TRY(Write(fd, crc, sizeof(crc)));
    793    MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
    794 
    795    // Align the start of the scripts section to the transcode alignment.
    796    size_t written = sizeof(MAGIC) + sizeof(headerSize) + buf.cursor();
    797    size_t padding = JS::AlignTranscodingBytecodeOffset(written) - written;
    798    if (padding) {
    799      MOZ_TRY(WritePadding(fd, padding));
    800      written += padding;
    801    }
    802 
    803    for (auto& scriptRef : scriptRefs) {
    804      auto* script = scriptRef.mStencil;
    805      MOZ_DIAGNOSTIC_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(written));
    806      MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
    807 
    808      written += script->mSize;
    809      // We can only free the XDR data if the stencil isn't borrowing data from
    810      // it.
    811      if (script->mStencil && !JS::StencilIsBorrowed(script->mStencil)) {
    812        script->FreeData();
    813      }
    814    }
    815  }
    816 
    817  MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + u".bin"_ns));
    818 
    819  return Ok();
    820 }
    821 
    822 nsresult ScriptPreloader::GetName(nsACString& aName) {
    823  aName.AssignLiteral("ScriptPreloader");
    824  return NS_OK;
    825 }
    826 
    827 // Runs in the mSaveThread thread, and writes out the cache file for the next
    828 // session after a reasonable delay.
    829 nsresult ScriptPreloader::Run() {
    830  MonitorAutoLock mal(mSaveMonitor.Lock());
    831  mSaveMonitor.NoteLockHeld();
    832 
    833  // Ideally wait about 10 seconds before saving, to avoid unnecessary IO
    834  // during early startup. But only if the cache hasn't been invalidated,
    835  // since that can trigger a new write during shutdown, and we don't want to
    836  // cause shutdown hangs.
    837  if (!mCacheInvalidated) {
    838    mal.Wait(TimeDuration::FromSeconds(10));
    839  }
    840 
    841  auto result = URLPreloader::GetSingleton().WriteCache();
    842  (void)NS_WARN_IF(result.isErr());
    843 
    844  result = WriteCache();
    845  (void)NS_WARN_IF(result.isErr());
    846 
    847  {
    848    MonitorAutoLock lock(mChildCache->mSaveMonitor.Lock());
    849    result = mChildCache->WriteCache();
    850  }
    851  (void)NS_WARN_IF(result.isErr());
    852 
    853  NS_DispatchToMainThread(
    854      NewRunnableMethod("ScriptPreloader::CacheWriteComplete", this,
    855                        &ScriptPreloader::CacheWriteComplete),
    856      NS_DISPATCH_NORMAL);
    857  return NS_OK;
    858 }
    859 
    860 void ScriptPreloader::CacheWriteComplete() {
    861  mSaveThread->AsyncShutdown();
    862  mSaveThread = nullptr;
    863  mSaveComplete = true;
    864 
    865  nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
    866  barrier->RemoveBlocker(this);
    867 }
    868 
    869 void ScriptPreloader::NoteStencil(const nsCString& url,
    870                                  const nsCString& cachePath,
    871                                  JS::Stencil* stencil, bool isRunOnce) {
    872  if (!Active()) {
    873    if (isRunOnce) {
    874      if (auto script = mScripts.Get(cachePath)) {
    875        script->mIsRunOnce = true;
    876        script->MaybeDropStencil();
    877      }
    878    }
    879    return;
    880  }
    881 
    882  // Don't bother trying to cache any URLs with cache-busting query
    883  // parameters.
    884  if (cachePath.FindChar('?') >= 0) {
    885    return;
    886  }
    887 
    888  // Don't bother caching files that belong to the mochitest harness.
    889  constexpr auto mochikitPrefix = "chrome://mochikit/"_ns;
    890  if (StringHead(url, mochikitPrefix.Length()) == mochikitPrefix) {
    891    return;
    892  }
    893 
    894  auto* script =
    895      mScripts.GetOrInsertNew(cachePath, *this, url, cachePath, stencil);
    896  if (isRunOnce) {
    897    script->mIsRunOnce = true;
    898  }
    899 
    900  if (!script->MaybeDropStencil() && !script->mStencil) {
    901    MOZ_ASSERT(stencil);
    902    script->mStencil = stencil;
    903    script->mReadyToExecute = true;
    904  }
    905 
    906  script->UpdateLoadTime(TimeStamp::Now());
    907  script->mProcessTypes += CurrentProcessType();
    908 }
    909 
    910 void ScriptPreloader::NoteStencil(const nsCString& url,
    911                                  const nsCString& cachePath,
    912                                  ProcessType processType,
    913                                  nsTArray<uint8_t>&& xdrData,
    914                                  TimeStamp loadTime) {
    915  // After data has been prepared, there's no point in noting further scripts,
    916  // since the cache either has already been written, or is about to be
    917  // written. Any time prior to the data being prepared, we can safely mutate
    918  // mScripts without locking. After that point, the save thread is free to
    919  // access it, and we can't alter it without locking.
    920  if (mDataPrepared) {
    921    return;
    922  }
    923 
    924  auto* script =
    925      mScripts.GetOrInsertNew(cachePath, *this, url, cachePath, nullptr);
    926 
    927  if (!script->HasRange()) {
    928    MOZ_ASSERT(!script->HasArray());
    929 
    930    script->mSize = xdrData.Length();
    931    script->mXDRData.construct<nsTArray<uint8_t>>(
    932        std::forward<nsTArray<uint8_t>>(xdrData));
    933 
    934    auto& data = script->Array();
    935    script->mXDRRange.emplace(data.Elements(), data.Length());
    936  }
    937 
    938  if (!script->mSize && !script->mStencil) {
    939    // If the content process is sending us an entry for a stencil
    940    // which was in the cache at startup, it expects us to already have this
    941    // script data, so it doesn't send it.
    942    //
    943    // However, the cache may have been invalidated at this point (usually
    944    // due to the add-on manager installing or uninstalling a legacy
    945    // extension during very early startup), which means we may no longer
    946    // have an entry for this script. Since that means we have no data to
    947    // write to the new cache, and no JSScript to generate it from, we need
    948    // to discard this entry.
    949    mScripts.Remove(cachePath);
    950    return;
    951  }
    952 
    953  script->UpdateLoadTime(loadTime);
    954  script->mProcessTypes += processType;
    955 }
    956 
    957 /* static */
    958 void ScriptPreloader::FillCompileOptionsForCachedStencil(
    959    JS::CompileOptions& options) {
    960  // Users of the cache do not require return values, so inform the JS parser in
    961  // order for it to generate simpler bytecode.
    962  options.setNoScriptRval(true);
    963 
    964  // The ScriptPreloader trades off having bytecode available but not source
    965  // text. This means the JS syntax-only parser is not used. If `toString` is
    966  // called on functions in these scripts, the source-hook will fetch it over,
    967  // so using `toString` of functions should be avoided in chrome js.
    968  options.setSourceIsLazy(true);
    969 }
    970 
    971 /* static */
    972 void ScriptPreloader::FillDecodeOptionsForCachedStencil(
    973    JS::DecodeOptions& options) {
    974  // ScriptPreloader's XDR buffer is alive during the Stencil is alive.
    975  // The decoded stencil can borrow from it.
    976  //
    977  // NOTE: The XDR buffer is alive during the entire browser lifetime only
    978  //       when it's mmapped.
    979  options.borrowBuffer = true;
    980 }
    981 
    982 already_AddRefed<JS::Stencil> ScriptPreloader::GetCachedStencil(
    983    JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
    984    const nsCString& path) {
    985  MOZ_RELEASE_ASSERT(
    986      !(XRE_IsContentProcess() && !mCacheInitialized),
    987      "ScriptPreloader must be initialized before getting cached "
    988      "scripts in the content process.");
    989 
    990  // If a script is used by both the parent and the child, it's stored only
    991  // in the child cache.
    992  if (mChildCache) {
    993    RefPtr<JS::Stencil> stencil =
    994        mChildCache->GetCachedStencilInternal(cx, options, path);
    995    if (stencil) {
    996      glean::script_preloader::requests
    997          .EnumGet(glean::script_preloader::RequestsLabel::eHitchild)
    998          .Add();
    999      return stencil.forget();
   1000    }
   1001  }
   1002 
   1003  RefPtr<JS::Stencil> stencil = GetCachedStencilInternal(cx, options, path);
   1004  glean::script_preloader::requests
   1005      .EnumGet(stencil ? glean::script_preloader::RequestsLabel::eHit
   1006                       : glean::script_preloader::RequestsLabel::eMiss)
   1007      .Add();
   1008 
   1009  return stencil.forget();
   1010 }
   1011 
   1012 already_AddRefed<JS::Stencil> ScriptPreloader::GetCachedStencilInternal(
   1013    JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
   1014    const nsCString& path) {
   1015  auto* cachedScript = mScripts.Get(path);
   1016  if (cachedScript) {
   1017    return WaitForCachedStencil(cx, options, cachedScript);
   1018  }
   1019  return nullptr;
   1020 }
   1021 
   1022 already_AddRefed<JS::Stencil> ScriptPreloader::WaitForCachedStencil(
   1023    JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
   1024    CachedStencil* script) {
   1025  if (!script->mReadyToExecute) {
   1026    // mReadyToExecute is kept as false only when off-thread decode task was
   1027    // available (pref is set to true) and the task was successfully created.
   1028    // See ScriptPreloader::StartDecodeTask methods.
   1029    MOZ_ASSERT(mDecodedStencils);
   1030 
   1031    // Check for the finished operations that can contain our target.
   1032    if (mDecodedStencils->AvailableRead() > 0) {
   1033      FinishOffThreadDecode();
   1034    }
   1035 
   1036    if (!script->mReadyToExecute) {
   1037      // Our target is not yet decoded.
   1038 
   1039      // If script is small enough, we'd rather decode on main-thread than wait
   1040      // for a decode task to complete.
   1041      if (script->mSize < MAX_MAINTHREAD_DECODE_SIZE) {
   1042        LOG(Info, "Script is small enough to recompile on main thread\n");
   1043 
   1044        script->mReadyToExecute = true;
   1045        glean::script_preloader::mainthread_recompile.Add(1);
   1046      } else {
   1047        LOG(Info, "Must wait for async script load: %s\n", script->mURL.get());
   1048        auto start = TimeStamp::Now();
   1049 
   1050        MonitorAutoLock mal(mMonitor);
   1051 
   1052        // Process finished tasks until our target is found.
   1053        while (!script->mReadyToExecute) {
   1054          if (mDecodedStencils->AvailableRead() > 0) {
   1055            FinishOffThreadDecode();
   1056          } else {
   1057            MOZ_ASSERT(!mDecodingScripts.isEmpty());
   1058            mWaitingForDecode = true;
   1059            mal.Wait();
   1060            mWaitingForDecode = false;
   1061          }
   1062        }
   1063 
   1064        TimeDuration waited = TimeStamp::Now() - start;
   1065        glean::script_preloader::wait_time.AccumulateRawDuration(waited);
   1066        LOG(Debug, "Waited %fms\n", waited.ToMilliseconds());
   1067      }
   1068    }
   1069  }
   1070 
   1071  return script->GetStencil(cx, options);
   1072 }
   1073 
   1074 void ScriptPreloader::onDecodedStencilQueued() {
   1075  mMonitor.AssertNotCurrentThreadOwns();
   1076  MonitorAutoLock mal(mMonitor);
   1077 
   1078  if (mWaitingForDecode) {
   1079    // Wake up the blocked main thread.
   1080    mal.Notify();
   1081  }
   1082 
   1083  // NOTE: Do not perform DoFinishOffThreadDecode for partial data.
   1084 }
   1085 
   1086 void ScriptPreloader::OnDecodeTaskFinished() {
   1087  mMonitor.AssertNotCurrentThreadOwns();
   1088  MonitorAutoLock mal(mMonitor);
   1089 
   1090  if (mWaitingForDecode) {
   1091    // Wake up the blocked main thread.
   1092    mal.Notify();
   1093  } else {
   1094    // Issue a Runnable to handle all decoded stencils, even if the next
   1095    // WaitForCachedStencil call has not happened yet.
   1096    NS_DispatchToMainThread(
   1097        NewRunnableMethod("ScriptPreloader::DoFinishOffThreadDecode", this,
   1098                          &ScriptPreloader::DoFinishOffThreadDecode));
   1099  }
   1100 }
   1101 
   1102 void ScriptPreloader::OnDecodeTaskFailed() {
   1103  // NOTE: nullptr is enqueued to mDecodedStencils, and FinishOffThreadDecode
   1104  //       handles it as failure.
   1105  OnDecodeTaskFinished();
   1106 }
   1107 
   1108 void ScriptPreloader::FinishPendingParses(MonitorAutoLock& aMal) {
   1109  mMonitor.AssertCurrentThreadOwns();
   1110 
   1111  // If off-thread decoding task hasn't been started, nothing to do.
   1112  // This can happen if the javascript.options.parallel_parsing pref was false,
   1113  // or the decode task fails to start.
   1114  if (!mDecodedStencils) {
   1115    return;
   1116  }
   1117 
   1118  // Process any pending decodes that are in flight.
   1119  while (!mDecodingScripts.isEmpty()) {
   1120    if (mDecodedStencils->AvailableRead() > 0) {
   1121      FinishOffThreadDecode();
   1122    } else {
   1123      mWaitingForDecode = true;
   1124      aMal.Wait();
   1125      mWaitingForDecode = false;
   1126    }
   1127  }
   1128 }
   1129 
   1130 void ScriptPreloader::DoFinishOffThreadDecode() {
   1131  // NOTE: mDecodedStencils could already be reset.
   1132  if (mDecodedStencils && mDecodedStencils->AvailableRead() > 0) {
   1133    FinishOffThreadDecode();
   1134  }
   1135 }
   1136 
   1137 void ScriptPreloader::FinishOffThreadDecode() {
   1138  MOZ_ASSERT(mDecodedStencils);
   1139 
   1140  while (mDecodedStencils->AvailableRead() > 0) {
   1141    RefPtr<JS::Stencil> stencil;
   1142    DebugOnly<int> reads = mDecodedStencils->Dequeue(&stencil, 1);
   1143    MOZ_ASSERT(reads == 1);
   1144 
   1145    if (!stencil) {
   1146      // DecodeTask failed.
   1147      // Mark all remaining scripts to be decoded on the main thread.
   1148      for (CachedStencil* next = mDecodingScripts.getFirst(); next;) {
   1149        auto* script = next;
   1150        next = script->getNext();
   1151 
   1152        script->mReadyToExecute = true;
   1153        script->remove();
   1154      }
   1155 
   1156      break;
   1157    }
   1158 
   1159    CachedStencil* script = mDecodingScripts.getFirst();
   1160    MOZ_ASSERT(script);
   1161 
   1162    LOG(Debug, "Finished off-thread decode of %s\n", script->mURL.get());
   1163    script->mStencil = stencil.forget();
   1164    script->mReadyToExecute = true;
   1165    script->remove();
   1166  }
   1167 
   1168  if (mDecodingScripts.isEmpty()) {
   1169    mDecodedStencils.reset();
   1170  }
   1171 }
   1172 
   1173 void ScriptPreloader::StartDecodeTask(JS::HandleObject scope) {
   1174  auto start = TimeStamp::Now();
   1175  LOG(Debug, "Off-thread decoding scripts...\n");
   1176 
   1177  Vector<JS::TranscodeSource> decodingSources;
   1178 
   1179  size_t size = 0;
   1180  for (CachedStencil* next = mDecodingScripts.getFirst(); next;) {
   1181    auto* script = next;
   1182    next = script->getNext();
   1183 
   1184    MOZ_ASSERT(script->IsMemMapped());
   1185 
   1186    // Skip any scripts that we decoded on the main thread rather than
   1187    // waiting for an off-thread operation to complete.
   1188    if (script->mReadyToExecute) {
   1189      script->remove();
   1190      continue;
   1191    }
   1192    if (!decodingSources.emplaceBack(script->Range(), script->mURL.get(), 0)) {
   1193      break;
   1194    }
   1195 
   1196    LOG(Debug, "Beginning off-thread decode of script %s (%u bytes)\n",
   1197        script->mURL.get(), script->mSize);
   1198 
   1199    size += script->mSize;
   1200  }
   1201 
   1202  MOZ_ASSERT(decodingSources.length() == mDecodingScripts.length());
   1203 
   1204  if (size == 0 && mDecodingScripts.isEmpty()) {
   1205    return;
   1206  }
   1207 
   1208  AutoSafeJSAPI jsapi;
   1209  JSContext* cx = jsapi.cx();
   1210  JSAutoRealm ar(cx, scope ? scope : xpc::CompilationScope());
   1211 
   1212  JS::CompileOptions options(cx);
   1213  FillCompileOptionsForCachedStencil(options);
   1214 
   1215  // All XDR buffers are mmapped and live longer than JS runtime.
   1216  // The bytecode can be borrowed from the buffer.
   1217  options.borrowBuffer = true;
   1218  options.usePinnedBytecode = true;
   1219 
   1220  JS::DecodeOptions decodeOptions(options);
   1221 
   1222  size_t decodingSourcesLength = decodingSources.length();
   1223 
   1224  if (!StaticPrefs::javascript_options_parallel_parsing() ||
   1225      !StartDecodeTask(decodeOptions, std::move(decodingSources))) {
   1226    LOG(Info, "Can't decode %lu bytes of scripts off-thread",
   1227        (unsigned long)size);
   1228    for (auto* script : mDecodingScripts) {
   1229      script->mReadyToExecute = true;
   1230    }
   1231    return;
   1232  }
   1233 
   1234  LOG(Debug, "Initialized decoding of %u scripts (%u bytes) in %fms\n",
   1235      (unsigned)decodingSourcesLength, (unsigned)size,
   1236      (TimeStamp::Now() - start).ToMilliseconds());
   1237 }
   1238 
   1239 bool ScriptPreloader::StartDecodeTask(
   1240    const JS::ReadOnlyDecodeOptions& decodeOptions,
   1241    Vector<JS::TranscodeSource>&& decodingSources) {
   1242  mDecodedStencils.emplace(decodingSources.length());
   1243  MOZ_ASSERT(mDecodedStencils);
   1244 
   1245  nsCOMPtr<nsIRunnable> task =
   1246      new DecodeTask(this, decodeOptions, std::move(decodingSources));
   1247 
   1248  nsresult rv = NS_DispatchBackgroundTask(task.forget());
   1249 
   1250  return NS_SUCCEEDED(rv);
   1251 }
   1252 
   1253 NS_IMETHODIMP ScriptPreloader::DecodeTask::Run() {
   1254  auto failure = [&]() {
   1255    RefPtr<JS::Stencil> stencil;
   1256    DebugOnly<int> writes = mPreloader->mDecodedStencils->Enqueue(stencil);
   1257    MOZ_ASSERT(writes == 1);
   1258    mPreloader->OnDecodeTaskFailed();
   1259  };
   1260 
   1261  JS::FrontendContext* fc = JS::NewFrontendContext();
   1262  if (!fc) {
   1263    failure();
   1264    return NS_OK;
   1265  }
   1266 
   1267  auto cleanup = MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
   1268 
   1269  size_t stackSize = TaskController::GetThreadStackSize();
   1270  JS::SetNativeStackQuota(fc, JS::ThreadStackQuotaForSize(stackSize));
   1271 
   1272  size_t remaining = mDecodingSources.length();
   1273  for (auto& source : mDecodingSources) {
   1274    RefPtr<JS::Stencil> stencil;
   1275    auto result = JS::DecodeStencil(fc, mDecodeOptions, source.range,
   1276                                    getter_AddRefs(stencil));
   1277    if (result != JS::TranscodeResult::Ok) {
   1278      failure();
   1279      return NS_OK;
   1280    }
   1281 
   1282    DebugOnly<int> writes = mPreloader->mDecodedStencils->Enqueue(stencil);
   1283    MOZ_ASSERT(writes == 1);
   1284 
   1285    remaining--;
   1286    if (remaining) {
   1287      mPreloader->onDecodedStencilQueued();
   1288    }
   1289  }
   1290 
   1291  mPreloader->OnDecodeTaskFinished();
   1292  return NS_OK;
   1293 }
   1294 
   1295 ScriptPreloader::CachedStencil::CachedStencil(ScriptPreloader& cache,
   1296                                              InputBuffer& buf)
   1297    : mCache(cache) {
   1298  Code(buf);
   1299 
   1300  // Swap the mProcessTypes and mOriginalProcessTypes values, since we want to
   1301  // start with an empty set of processes loaded into for this session, and
   1302  // compare against last session's values later.
   1303  mOriginalProcessTypes = mProcessTypes;
   1304  mProcessTypes = {};
   1305 }
   1306 
   1307 bool ScriptPreloader::CachedStencil::XDREncode(JS::FrontendContext* aFc) {
   1308  auto cleanup = MakeScopeExit([&]() { MaybeDropStencil(); });
   1309 
   1310  mXDRData.construct<JS::TranscodeBuffer>();
   1311 
   1312  JS::TranscodeResult code = JS::EncodeStencil(aFc, mStencil, Buffer());
   1313 
   1314  if (code == JS::TranscodeResult::Ok) {
   1315    mXDRRange.emplace(Buffer().begin(), Buffer().length());
   1316    mSize = Range().length();
   1317    return true;
   1318  }
   1319  mXDRData.destroy();
   1320  JS::ClearFrontendErrors(aFc);
   1321  return false;
   1322 }
   1323 
   1324 already_AddRefed<JS::Stencil> ScriptPreloader::CachedStencil::GetStencil(
   1325    JSContext* cx, const JS::ReadOnlyDecodeOptions& options) {
   1326  MOZ_ASSERT(mReadyToExecute);
   1327  if (mStencil) {
   1328    return do_AddRef(mStencil);
   1329  }
   1330 
   1331  if (!HasRange()) {
   1332    // We've already executed the script, and thrown it away. But it wasn't
   1333    // in the cache at startup, so we don't have any data to decode. Give
   1334    // up.
   1335    return nullptr;
   1336  }
   1337 
   1338  // If we have no script at this point, the script was too small to decode
   1339  // off-thread, or it was needed before the off-thread compilation was
   1340  // finished, and is small enough to decode on the main thread rather than
   1341  // wait for the off-thread decoding to finish. In either case, we decode
   1342  // it synchronously the first time it's needed.
   1343 
   1344  auto start = TimeStamp::Now();
   1345  LOG(Info, "Decoding stencil %s on main thread...\n", mURL.get());
   1346 
   1347  RefPtr<JS::Stencil> stencil;
   1348  if (JS::DecodeStencil(cx, options, Range(), getter_AddRefs(stencil)) ==
   1349      JS::TranscodeResult::Ok) {
   1350    // Lock the monitor here to avoid data races on mScript
   1351    // from other threads like the cache writing thread.
   1352    //
   1353    // It is possible that we could end up decoding the same
   1354    // script twice, because DecodeScript isn't being guarded
   1355    // by the monitor; however, to encourage off-thread decode
   1356    // to proceed for other scripts we don't hold the monitor
   1357    // while doing main thread decode, merely while updating
   1358    // mScript.
   1359    mCache.mMonitor.AssertNotCurrentThreadOwns();
   1360    MonitorAutoLock mal(mCache.mMonitor);
   1361 
   1362    mStencil = stencil.forget();
   1363 
   1364    if (mCache.mSaveComplete) {
   1365      // We can only free XDR data if the stencil isn't borrowing data out of
   1366      // it.
   1367      if (!JS::StencilIsBorrowed(mStencil)) {
   1368        FreeData();
   1369      }
   1370    }
   1371  }
   1372 
   1373  LOG(Debug, "Finished decoding in %fms",
   1374      (TimeStamp::Now() - start).ToMilliseconds());
   1375 
   1376  return do_AddRef(mStencil);
   1377 }
   1378 
   1379 // nsIAsyncShutdownBlocker
   1380 
   1381 nsresult ScriptPreloader::GetName(nsAString& aName) {
   1382  aName.AssignLiteral(u"ScriptPreloader: Saving bytecode cache");
   1383  return NS_OK;
   1384 }
   1385 
   1386 nsresult ScriptPreloader::GetState(nsIPropertyBag** aState) {
   1387  *aState = nullptr;
   1388  return NS_OK;
   1389 }
   1390 
   1391 nsresult ScriptPreloader::BlockShutdown(
   1392    nsIAsyncShutdownClient* aBarrierClient) {
   1393  // If we're waiting on a timeout to finish saving, interrupt it and just save
   1394  // immediately.
   1395  mSaveMonitor.Lock().NotifyAll();
   1396  return NS_OK;
   1397 }
   1398 
   1399 already_AddRefed<nsIAsyncShutdownClient> ScriptPreloader::GetShutdownBarrier() {
   1400  nsCOMPtr<nsIAsyncShutdownService> svc = components::AsyncShutdown::Service();
   1401  MOZ_RELEASE_ASSERT(svc);
   1402 
   1403  nsCOMPtr<nsIAsyncShutdownClient> barrier;
   1404  (void)svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
   1405  MOZ_RELEASE_ASSERT(barrier);
   1406 
   1407  return barrier.forget();
   1408 }
   1409 
   1410 NS_IMPL_ISUPPORTS(ScriptPreloader, nsIObserver, nsIRunnable, nsIMemoryReporter,
   1411                  nsINamed, nsIAsyncShutdownBlocker)
   1412 
   1413 #undef LOG
   1414 
   1415 }  // namespace mozilla