Bug 1943761 - Add class alignment to the mozsearch analysis file. r=asuth
[gecko.git] / js / xpconnect / loader / ScriptPreloader.cpp
blobfe167cb8f247113d0130b271c853d1db2d265a7e
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/. */
7 #include "ScriptPreloader-inl.h"
8 #include "mozilla/AlreadyAddRefed.h"
9 #include "mozilla/Monitor.h"
11 #include "mozilla/ScriptPreloader.h"
12 #include "mozilla/loader/ScriptCacheActors.h"
14 #include "mozilla/URLPreloader.h"
16 #include "mozilla/ArrayUtils.h"
17 #include "mozilla/Components.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/FileUtils.h"
20 #include "mozilla/IOBuffers.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/StaticPrefs_javascript.h"
25 #include "mozilla/TaskController.h"
26 #include "mozilla/glean/JsXpconnectMetrics.h"
27 #include "mozilla/Telemetry.h"
28 #include "mozilla/Try.h"
29 #include "mozilla/Unused.h"
30 #include "mozilla/dom/ContentChild.h"
31 #include "mozilla/dom/ContentParent.h"
32 #include "mozilla/dom/Document.h"
33 #include "mozilla/scache/StartupCache.h"
35 #include "crc32c.h"
36 #include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions
37 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::DecodeStencil
38 #include "js/experimental/CompileScript.h" // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::SetNativeStackQuota, JS::ThreadStackQuotaForSize
39 #include "js/Transcoding.h"
40 #include "MainThreadUtils.h"
41 #include "nsDebug.h"
42 #include "nsDirectoryServiceUtils.h"
43 #include "nsIFile.h"
44 #include "nsIObserverService.h"
45 #include "nsJSUtils.h"
46 #include "nsMemoryReporterManager.h"
47 #include "nsNetUtil.h"
48 #include "nsProxyRelease.h"
49 #include "nsThreadUtils.h"
50 #include "nsXULAppAPI.h"
51 #include "xpcpublic.h"
53 #define STARTUP_COMPLETE_TOPIC "browser-delayed-startup-finished"
54 #define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
55 #define CONTENT_DOCUMENT_LOADED_TOPIC "content-document-loaded"
56 #define CACHE_WRITE_TOPIC "browser-idle-startup-tasks-finished"
57 #define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"
58 #define CACHE_INVALIDATE_TOPIC "startupcache-invalidate"
60 // The maximum time we'll wait for a child process to finish starting up before
61 // we send its script data back to the parent.
62 constexpr uint32_t CHILD_STARTUP_TIMEOUT_MS = 8000;
64 namespace mozilla {
65 namespace {
66 static LazyLogModule gLog("ScriptPreloader");
68 #define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
69 } // namespace
71 using mozilla::dom::AutoJSAPI;
72 using mozilla::dom::ContentChild;
73 using mozilla::dom::ContentParent;
74 using namespace mozilla::loader;
75 using mozilla::scache::StartupCache;
77 using namespace JS;
79 ProcessType ScriptPreloader::sProcessType;
81 nsresult ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
82 nsISupports* aData, bool aAnonymize) {
83 MOZ_COLLECT_REPORT(
84 "explicit/script-preloader/heap/saved-scripts", KIND_HEAP, UNITS_BYTES,
85 SizeOfHashEntries<ScriptStatus::Saved>(mScripts, MallocSizeOf),
86 "Memory used to hold the scripts which have been executed in this "
87 "session, and will be written to the startup script cache file.");
89 MOZ_COLLECT_REPORT(
90 "explicit/script-preloader/heap/restored-scripts", KIND_HEAP, UNITS_BYTES,
91 SizeOfHashEntries<ScriptStatus::Restored>(mScripts, MallocSizeOf),
92 "Memory used to hold the scripts which have been restored from the "
93 "startup script cache file, but have not been executed in this session.");
95 MOZ_COLLECT_REPORT("explicit/script-preloader/heap/other", KIND_HEAP,
96 UNITS_BYTES, ShallowHeapSizeOfIncludingThis(MallocSizeOf),
97 "Memory used by the script cache service itself.");
99 // Since the mem-mapped cache file is mapped into memory, we want to report
100 // it as explicit memory somewhere. But since the child cache is shared
101 // between all processes, we don't want to report it as explicit memory for
102 // all of them. So we report it as explicit only in the parent process, and
103 // non-explicit everywhere else.
104 if (XRE_IsParentProcess()) {
105 MOZ_COLLECT_REPORT("explicit/script-preloader/non-heap/memmapped-cache",
106 KIND_NONHEAP, UNITS_BYTES,
107 mCacheData->nonHeapSizeOfExcludingThis(),
108 "The memory-mapped startup script cache file.");
109 } else {
110 MOZ_COLLECT_REPORT("script-preloader-memmapped-cache", KIND_NONHEAP,
111 UNITS_BYTES, mCacheData->nonHeapSizeOfExcludingThis(),
112 "The memory-mapped startup script cache file.");
115 return NS_OK;
118 StaticRefPtr<ScriptPreloader> ScriptPreloader::gScriptPreloader;
119 StaticRefPtr<ScriptPreloader> ScriptPreloader::gChildScriptPreloader;
120 StaticAutoPtr<AutoMemMap> ScriptPreloader::gCacheData;
121 StaticAutoPtr<AutoMemMap> ScriptPreloader::gChildCacheData;
123 ScriptPreloader& ScriptPreloader::GetSingleton() {
124 if (!gScriptPreloader) {
125 if (XRE_IsParentProcess()) {
126 gCacheData = new AutoMemMap();
127 gScriptPreloader = new ScriptPreloader(gCacheData.get());
128 gScriptPreloader->mChildCache = &GetChildSingleton();
129 Unused << gScriptPreloader->InitCache();
130 } else {
131 gScriptPreloader = &GetChildSingleton();
135 return *gScriptPreloader;
138 // The child singleton is available in all processes, including the parent, and
139 // is used for scripts which are expected to be loaded into child processes
140 // (such as process and frame scripts), or scripts that have already been loaded
141 // into a child. The child caches are managed as follows:
143 // - Every startup, we open the cache file from the last session, move it to a
144 // new location, and begin pre-loading the scripts that are stored in it. There
145 // is a separate cache file for parent and content processes, but the parent
146 // process opens both the parent and content cache files.
148 // - Once startup is complete, we write a new cache file for the next session,
149 // containing only the scripts that were used during early startup, so we
150 // don't waste pre-loading scripts that may not be needed.
152 // - For content processes, opening and writing the cache file is handled in the
153 // parent process. The first content process of each type sends back the data
154 // for scripts that were loaded in early startup, and the parent merges them
155 // and writes them to a cache file.
157 // - Currently, content processes only benefit from the cache data written
158 // during the *previous* session. Ideally, new content processes should
159 // probably use the cache data written during this session if there was no
160 // previous cache file, but I'd rather do that as a follow-up.
161 ScriptPreloader& ScriptPreloader::GetChildSingleton() {
162 if (!gChildScriptPreloader) {
163 gChildCacheData = new AutoMemMap();
164 gChildScriptPreloader = new ScriptPreloader(gChildCacheData.get());
165 if (XRE_IsParentProcess()) {
166 Unused << gChildScriptPreloader->InitCache(u"scriptCache-child"_ns);
170 return *gChildScriptPreloader;
173 /* static */
174 void ScriptPreloader::DeleteSingleton() {
175 gScriptPreloader = nullptr;
176 gChildScriptPreloader = nullptr;
179 /* static */
180 void ScriptPreloader::DeleteCacheDataSingleton() {
181 MOZ_ASSERT(!gScriptPreloader);
182 MOZ_ASSERT(!gChildScriptPreloader);
184 gCacheData = nullptr;
185 gChildCacheData = nullptr;
188 void ScriptPreloader::InitContentChild(ContentParent& parent) {
189 auto& cache = GetChildSingleton();
190 cache.mSaveMonitor.AssertOnWritingThread();
192 // We want startup script data from the first process of a given type.
193 // That process sends back its script data before it executes any
194 // untrusted code, and then we never accept further script data for that
195 // type of process for the rest of the session.
197 // The script data from each process type is merged with the data from the
198 // parent process's frame and process scripts, and shared between all
199 // content process types in the next session.
201 // Note that if the first process of a given type crashes or shuts down
202 // before sending us its script data, we silently ignore it, and data for
203 // that process type is not included in the next session's cache. This
204 // should be a sufficiently rare occurrence that it's not worth trying to
205 // handle specially.
206 auto processType = GetChildProcessType(parent.GetRemoteType());
207 bool wantScriptData = !cache.mInitializedProcesses.contains(processType);
208 cache.mInitializedProcesses += processType;
210 auto fd = cache.mCacheData->cloneFileDescriptor();
211 // Don't send original cache data to new processes if the cache has been
212 // invalidated.
213 if (fd.IsValid() && !cache.mCacheInvalidated) {
214 Unused << parent.SendPScriptCacheConstructor(fd, wantScriptData);
215 } else {
216 Unused << parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND,
217 wantScriptData);
221 ProcessType ScriptPreloader::GetChildProcessType(const nsACString& remoteType) {
222 if (remoteType == EXTENSION_REMOTE_TYPE) {
223 return ProcessType::Extension;
225 if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
226 return ProcessType::PrivilegedAbout;
228 return ProcessType::Web;
231 ScriptPreloader::ScriptPreloader(AutoMemMap* cacheData)
232 : mCacheData(cacheData),
233 mMonitor("[ScriptPreloader.mMonitor]"),
234 mSaveMonitor("[ScriptPreloader.mSaveMonitor]", this) {
235 // We do not set the process type for child processes here because the
236 // remoteType in ContentChild is not ready yet.
237 if (XRE_IsParentProcess()) {
238 sProcessType = ProcessType::Parent;
241 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
242 MOZ_RELEASE_ASSERT(obs);
244 if (XRE_IsParentProcess()) {
245 // In the parent process, we want to freeze the script cache as soon
246 // as idle tasks for the first browser window have completed.
247 obs->AddObserver(this, STARTUP_COMPLETE_TOPIC, false);
248 obs->AddObserver(this, CACHE_WRITE_TOPIC, false);
251 obs->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
252 obs->AddObserver(this, CACHE_INVALIDATE_TOPIC, false);
255 ScriptPreloader::~ScriptPreloader() { Cleanup(); }
257 void ScriptPreloader::Cleanup() {
258 mScripts.Clear();
259 UnregisterWeakMemoryReporter(this);
262 void ScriptPreloader::StartCacheWrite() {
263 MOZ_DIAGNOSTIC_ASSERT(!mSaveThread);
265 Unused << NS_NewNamedThread("SaveScripts", getter_AddRefs(mSaveThread), this);
267 nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
268 barrier->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
269 u""_ns);
272 void ScriptPreloader::InvalidateCache() {
274 mMonitor.AssertNotCurrentThreadOwns();
275 MonitorAutoLock mal(mMonitor);
277 // Wait for pending off-thread parses to finish, since they depend on the
278 // memory allocated by our CachedStencil, and can't be canceled
279 // asynchronously.
280 FinishPendingParses(mal);
282 // Pending scripts should have been cleared by the above, and the queue
283 // should have been reset.
284 MOZ_ASSERT(mDecodingScripts.isEmpty());
285 MOZ_ASSERT(!mDecodedStencils);
287 mScripts.Clear();
289 // If we've already finished saving the cache at this point, start a new
290 // delayed save operation. This will write out an empty cache file in place
291 // of any cache file we've already written out this session, which will
292 // prevent us from falling back to the current session's cache file on the
293 // next startup.
294 if (mSaveComplete && !mSaveThread && mChildCache) {
295 mSaveComplete = false;
297 StartCacheWrite();
302 MonitorSingleWriterAutoLock saveMonitorAutoLock(mSaveMonitor);
304 mCacheInvalidated = true;
307 // If we're waiting on a timeout to finish saving, interrupt it and just save
308 // immediately.
309 mSaveMonitor.NotifyAll();
312 nsresult ScriptPreloader::Observe(nsISupports* subject, const char* topic,
313 const char16_t* data) {
314 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
315 if (!strcmp(topic, STARTUP_COMPLETE_TOPIC)) {
316 obs->RemoveObserver(this, STARTUP_COMPLETE_TOPIC);
318 MOZ_ASSERT(XRE_IsParentProcess());
320 mStartupFinished = true;
321 URLPreloader::GetSingleton().SetStartupFinished();
322 } else if (!strcmp(topic, CACHE_WRITE_TOPIC)) {
323 obs->RemoveObserver(this, CACHE_WRITE_TOPIC);
325 MOZ_ASSERT(mStartupFinished);
326 MOZ_ASSERT(XRE_IsParentProcess());
328 if (mChildCache && !mSaveComplete && !mSaveThread) {
329 StartCacheWrite();
331 } else if (mContentStartupFinishedTopic.Equals(topic)) {
332 // If this is an uninitialized about:blank viewer or a chrome: document
333 // (which should always be an XBL binding document), ignore it. We don't
334 // have to worry about it loading malicious content.
335 if (nsCOMPtr<dom::Document> doc = do_QueryInterface(subject)) {
336 nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
338 if ((NS_IsAboutBlank(uri) &&
339 doc->GetReadyStateEnum() == doc->READYSTATE_UNINITIALIZED) ||
340 uri->SchemeIs("chrome")) {
341 return NS_OK;
344 FinishContentStartup();
345 } else if (!strcmp(topic, "timer-callback")) {
346 FinishContentStartup();
347 } else if (!strcmp(topic, XPCOM_SHUTDOWN_TOPIC)) {
348 // Wait for any pending parses to finish at this point, to avoid creating
349 // new stencils during destroying the JS runtime.
350 MonitorAutoLock mal(mMonitor);
351 FinishPendingParses(mal);
352 } else if (!strcmp(topic, CACHE_INVALIDATE_TOPIC)) {
353 InvalidateCache();
356 return NS_OK;
359 void ScriptPreloader::FinishContentStartup() {
360 MOZ_ASSERT(XRE_IsContentProcess());
362 #ifdef DEBUG
363 if (mContentStartupFinishedTopic.Equals(CONTENT_DOCUMENT_LOADED_TOPIC)) {
364 MOZ_ASSERT(sProcessType == ProcessType::PrivilegedAbout);
365 } else {
366 MOZ_ASSERT(sProcessType != ProcessType::PrivilegedAbout);
368 #endif /* DEBUG */
370 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
371 obs->RemoveObserver(this, mContentStartupFinishedTopic.get());
373 mSaveTimer = nullptr;
375 mStartupFinished = true;
377 if (mChildActor) {
378 mChildActor->SendScriptsAndFinalize(mScripts);
381 #ifdef XP_WIN
382 // Record the amount of USS at startup. This is Windows-only for now,
383 // we could turn it on for Linux relatively cheaply. On macOS it can have
384 // a perf impact. Only record this for non-privileged processes because
385 // privileged processes record this value at a different time, leading to
386 // a higher value which skews the telemetry.
387 if (sProcessType != ProcessType::PrivilegedAbout) {
388 mozilla::Telemetry::Accumulate(
389 mozilla::Telemetry::MEMORY_UNIQUE_CONTENT_STARTUP,
390 nsMemoryReporterManager::ResidentUnique() / 1024);
392 #endif
395 bool ScriptPreloader::WillWriteScripts() {
396 return !mDataPrepared && (XRE_IsParentProcess() || mChildActor);
399 Result<nsCOMPtr<nsIFile>, nsresult> ScriptPreloader::GetCacheFile(
400 const nsAString& suffix) {
401 NS_ENSURE_TRUE(mProfD, Err(NS_ERROR_NOT_INITIALIZED));
403 nsCOMPtr<nsIFile> cacheFile;
404 MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
406 MOZ_TRY(cacheFile->AppendNative("startupCache"_ns));
407 Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
409 MOZ_TRY(cacheFile->Append(mBaseName + suffix));
411 return std::move(cacheFile);
414 static const uint8_t MAGIC[] = "mozXDRcachev003";
416 Result<Ok, nsresult> ScriptPreloader::OpenCache() {
417 if (StartupCache::GetIgnoreDiskCache()) {
418 return Err(NS_ERROR_ABORT);
421 MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
423 nsCOMPtr<nsIFile> cacheFile;
424 MOZ_TRY_VAR(cacheFile, GetCacheFile(u".bin"_ns));
426 bool exists;
427 MOZ_TRY(cacheFile->Exists(&exists));
428 if (exists) {
429 MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + u"-current.bin"_ns));
430 } else {
431 MOZ_TRY(cacheFile->SetLeafName(mBaseName + u"-current.bin"_ns));
432 MOZ_TRY(cacheFile->Exists(&exists));
433 if (!exists) {
434 return Err(NS_ERROR_FILE_NOT_FOUND);
438 MOZ_TRY(mCacheData->init(cacheFile));
440 return Ok();
443 // Opens the script cache file for this session, and initializes the script
444 // cache based on its contents. See WriteCache for details of the cache file.
445 Result<Ok, nsresult> ScriptPreloader::InitCache(const nsAString& basePath) {
446 mSaveMonitor.AssertOnWritingThread();
447 mCacheInitialized = true;
448 mBaseName = basePath;
450 RegisterWeakMemoryReporter(this);
452 if (!XRE_IsParentProcess()) {
453 return Ok();
456 // Grab the compilation scope before initializing the URLPreloader, since
457 // it's not safe to run component loader code during its critical section.
458 AutoSafeJSAPI jsapi;
459 JS::RootedObject scope(jsapi.cx(), xpc::CompilationScope());
461 // Note: Code on the main thread *must not access Omnijar in any way* until
462 // this AutoBeginReading guard is destroyed.
463 URLPreloader::AutoBeginReading abr;
465 MOZ_TRY(OpenCache());
467 return InitCacheInternal(scope);
470 Result<Ok, nsresult> ScriptPreloader::InitCache(
471 const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild) {
472 mSaveMonitor.AssertOnWritingThread();
473 MOZ_ASSERT(XRE_IsContentProcess());
475 mCacheInitialized = true;
476 mChildActor = cacheChild;
477 sProcessType =
478 GetChildProcessType(dom::ContentChild::GetSingleton()->GetRemoteType());
480 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
481 MOZ_RELEASE_ASSERT(obs);
483 if (sProcessType == ProcessType::PrivilegedAbout) {
484 // Since we control all of the documents loaded in the privileged
485 // content process, we can increase the window of active time for the
486 // ScriptPreloader to include the scripts that are loaded until the
487 // first document finishes loading.
488 mContentStartupFinishedTopic.AssignLiteral(CONTENT_DOCUMENT_LOADED_TOPIC);
489 } else {
490 // In the child process, we need to freeze the script cache before any
491 // untrusted code has been executed. The insertion of the first DOM
492 // document element may sometimes be earlier than is ideal, but at
493 // least it should always be safe.
494 mContentStartupFinishedTopic.AssignLiteral(DOC_ELEM_INSERTED_TOPIC);
496 obs->AddObserver(this, mContentStartupFinishedTopic.get(), false);
498 RegisterWeakMemoryReporter(this);
500 auto cleanup = MakeScopeExit([&] {
501 // If the parent is expecting cache data from us, make sure we send it
502 // before it writes out its cache file. For normal proceses, this isn't
503 // a concern, since they begin loading documents quite early. For the
504 // preloaded process, we may end up waiting a long time (or, indeed,
505 // never loading a document), so we need an additional timeout.
506 if (cacheChild) {
507 NS_NewTimerWithObserver(getter_AddRefs(mSaveTimer), this,
508 CHILD_STARTUP_TIMEOUT_MS,
509 nsITimer::TYPE_ONE_SHOT);
513 if (cacheFile.isNothing()) {
514 return Ok();
517 MOZ_TRY(mCacheData->init(cacheFile.ref()));
519 return InitCacheInternal();
522 Result<Ok, nsresult> ScriptPreloader::InitCacheInternal(
523 JS::HandleObject scope) {
524 auto size = mCacheData->size();
526 uint32_t headerSize;
527 uint32_t crc;
528 if (size < sizeof(MAGIC) + sizeof(headerSize) + sizeof(crc)) {
529 return Err(NS_ERROR_UNEXPECTED);
532 auto data = mCacheData->get<uint8_t>();
533 MOZ_RELEASE_ASSERT(JS::IsTranscodingBytecodeAligned(data.get()));
535 auto end = data + size;
537 if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) {
538 return Err(NS_ERROR_UNEXPECTED);
540 data += sizeof(MAGIC);
542 headerSize = LittleEndian::readUint32(data.get());
543 data += sizeof(headerSize);
545 crc = LittleEndian::readUint32(data.get());
546 data += sizeof(crc);
548 if (data + headerSize > end) {
549 return Err(NS_ERROR_UNEXPECTED);
552 if (crc != ComputeCrc32c(~0, data.get(), headerSize)) {
553 return Err(NS_ERROR_UNEXPECTED);
557 auto cleanup = MakeScopeExit([&]() { mScripts.Clear(); });
559 LinkedList<CachedStencil> scripts;
561 Range<uint8_t> header(data, data + headerSize);
562 data += headerSize;
564 // Reconstruct alignment padding if required.
565 size_t currentOffset = data - mCacheData->get<uint8_t>();
566 data += JS::AlignTranscodingBytecodeOffset(currentOffset) - currentOffset;
568 InputBuffer buf(header);
570 size_t offset = 0;
571 while (!buf.finished()) {
572 auto script = MakeUnique<CachedStencil>(*this, buf);
573 MOZ_RELEASE_ASSERT(script);
575 auto scriptData = data + script->mOffset;
576 if (!JS::IsTranscodingBytecodeAligned(scriptData.get())) {
577 return Err(NS_ERROR_UNEXPECTED);
580 if (scriptData + script->mSize > end) {
581 return Err(NS_ERROR_UNEXPECTED);
584 // Make sure offsets match what we'd expect based on script ordering and
585 // size, as a basic sanity check.
586 if (script->mOffset != offset) {
587 return Err(NS_ERROR_UNEXPECTED);
589 offset += script->mSize;
591 script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
593 // Don't pre-decode the script unless it was used in this process type
594 // during the previous session.
595 if (script->mOriginalProcessTypes.contains(CurrentProcessType())) {
596 scripts.insertBack(script.get());
597 } else {
598 script->mReadyToExecute = true;
601 const auto& cachePath = script->mCachePath;
602 mScripts.InsertOrUpdate(cachePath, std::move(script));
605 if (buf.error()) {
606 return Err(NS_ERROR_UNEXPECTED);
609 mDecodingScripts = std::move(scripts);
610 cleanup.release();
613 StartDecodeTask(scope);
614 return Ok();
617 void ScriptPreloader::PrepareCacheWriteInternal() {
618 MOZ_ASSERT(NS_IsMainThread());
620 mMonitor.AssertCurrentThreadOwns();
622 auto cleanup = MakeScopeExit([&]() {
623 if (mChildCache) {
624 mChildCache->PrepareCacheWrite();
628 if (mDataPrepared) {
629 return;
632 AutoSafeJSAPI jsapi;
633 JSAutoRealm ar(jsapi.cx(), xpc::PrivilegedJunkScope());
634 bool found = false;
635 for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
636 // Don't write any scripts that are also in the child cache. They'll be
637 // loaded from the child cache in that case, so there's no need to write
638 // them twice.
639 CachedStencil* childScript =
640 mChildCache ? mChildCache->mScripts.Get(script->mCachePath) : nullptr;
641 if (childScript && !childScript->mProcessTypes.isEmpty()) {
642 childScript->UpdateLoadTime(script->mLoadTime);
643 childScript->mProcessTypes += script->mProcessTypes;
644 script.Remove();
645 continue;
648 if (!(script->mProcessTypes == script->mOriginalProcessTypes)) {
649 // Note: EnumSet doesn't support operator!=, hence the weird form above.
650 found = true;
653 if (!script->mSize && !script->XDREncode(jsapi.cx())) {
654 script.Remove();
658 if (!found) {
659 mSaveComplete = true;
660 return;
663 mDataPrepared = true;
666 void ScriptPreloader::PrepareCacheWrite() {
667 MonitorAutoLock mal(mMonitor);
669 PrepareCacheWriteInternal();
672 // Writes out a script cache file for the scripts accessed during early
673 // startup in this session. The cache file is a little-endian binary file with
674 // the following format:
676 // - A uint32 containing the size of the header block.
678 // - A header entry for each file stored in the cache containing:
679 // - The URL that the script was originally read from.
680 // - Its cache key.
681 // - The offset of its XDR data within the XDR data block.
682 // - The size of its XDR data in the XDR data block.
683 // - A bit field describing which process types the script is used in.
685 // - A block of XDR data for the encoded scripts, with each script's data at
686 // an offset from the start of the block, as specified above.
687 Result<Ok, nsresult> ScriptPreloader::WriteCache() {
688 MOZ_ASSERT(!NS_IsMainThread());
689 mSaveMonitor.AssertCurrentThreadOwns();
691 if (!mDataPrepared && !mSaveComplete) {
692 MonitorSingleWriterAutoUnlock mau(mSaveMonitor);
694 NS_DispatchAndSpinEventLoopUntilComplete(
695 "ScriptPreloader::PrepareCacheWrite"_ns,
696 GetMainThreadSerialEventTarget(),
697 NewRunnableMethod("ScriptPreloader::PrepareCacheWrite", this,
698 &ScriptPreloader::PrepareCacheWrite));
701 if (mSaveComplete) {
702 // If we don't have anything we need to save, we're done.
703 return Ok();
706 nsCOMPtr<nsIFile> cacheFile;
707 MOZ_TRY_VAR(cacheFile, GetCacheFile(u"-new.bin"_ns));
709 bool exists;
710 MOZ_TRY(cacheFile->Exists(&exists));
711 if (exists) {
712 MOZ_TRY(cacheFile->Remove(false));
716 AutoFDClose raiiFd;
717 MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644,
718 getter_Transfers(raiiFd)));
719 const auto fd = raiiFd.get();
721 // We also need to hold mMonitor while we're touching scripts in
722 // mScripts, or they may be freed before we're done with them.
723 mMonitor.AssertNotCurrentThreadOwns();
724 MonitorAutoLock mal(mMonitor);
726 nsTArray<CachedStencil*> scripts;
727 for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
728 scripts.AppendElement(script);
731 // Sort scripts by load time, with async loaded scripts before sync scripts.
732 // Since async scripts are always loaded immediately at startup, it helps to
733 // have them stored contiguously.
734 scripts.Sort(CachedStencil::Comparator());
736 OutputBuffer buf;
737 size_t offset = 0;
738 for (auto script : scripts) {
739 script->mOffset = offset;
740 MOZ_DIAGNOSTIC_ASSERT(
741 JS::IsTranscodingBytecodeOffsetAligned(script->mOffset));
742 script->Code(buf);
744 offset += script->mSize;
745 MOZ_DIAGNOSTIC_ASSERT(
746 JS::IsTranscodingBytecodeOffsetAligned(script->mSize));
749 uint8_t headerSize[4];
750 LittleEndian::writeUint32(headerSize, buf.cursor());
752 uint8_t crc[4];
753 LittleEndian::writeUint32(crc, ComputeCrc32c(~0, buf.Get(), buf.cursor()));
755 MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
756 MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
757 MOZ_TRY(Write(fd, crc, sizeof(crc)));
758 MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
760 // Align the start of the scripts section to the transcode alignment.
761 size_t written = sizeof(MAGIC) + sizeof(headerSize) + buf.cursor();
762 size_t padding = JS::AlignTranscodingBytecodeOffset(written) - written;
763 if (padding) {
764 MOZ_TRY(WritePadding(fd, padding));
765 written += padding;
768 for (auto script : scripts) {
769 MOZ_DIAGNOSTIC_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(written));
770 MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
772 written += script->mSize;
773 // We can only free the XDR data if the stencil isn't borrowing data from
774 // it.
775 if (script->mStencil && !JS::StencilIsBorrowed(script->mStencil)) {
776 script->FreeData();
781 MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + u".bin"_ns));
783 return Ok();
786 nsresult ScriptPreloader::GetName(nsACString& aName) {
787 aName.AssignLiteral("ScriptPreloader");
788 return NS_OK;
791 // Runs in the mSaveThread thread, and writes out the cache file for the next
792 // session after a reasonable delay.
793 nsresult ScriptPreloader::Run() {
794 MonitorSingleWriterAutoLock mal(mSaveMonitor);
796 // Ideally wait about 10 seconds before saving, to avoid unnecessary IO
797 // during early startup. But only if the cache hasn't been invalidated,
798 // since that can trigger a new write during shutdown, and we don't want to
799 // cause shutdown hangs.
800 if (!mCacheInvalidated) {
801 mal.Wait(TimeDuration::FromSeconds(10));
804 auto result = URLPreloader::GetSingleton().WriteCache();
805 Unused << NS_WARN_IF(result.isErr());
807 result = WriteCache();
808 Unused << NS_WARN_IF(result.isErr());
811 MonitorSingleWriterAutoLock lock(mChildCache->mSaveMonitor);
812 result = mChildCache->WriteCache();
814 Unused << NS_WARN_IF(result.isErr());
816 NS_DispatchToMainThread(
817 NewRunnableMethod("ScriptPreloader::CacheWriteComplete", this,
818 &ScriptPreloader::CacheWriteComplete),
819 NS_DISPATCH_NORMAL);
820 return NS_OK;
823 void ScriptPreloader::CacheWriteComplete() {
824 mSaveThread->AsyncShutdown();
825 mSaveThread = nullptr;
826 mSaveComplete = true;
828 nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
829 barrier->RemoveBlocker(this);
832 void ScriptPreloader::NoteStencil(const nsCString& url,
833 const nsCString& cachePath,
834 JS::Stencil* stencil, bool isRunOnce) {
835 if (!Active()) {
836 if (isRunOnce) {
837 if (auto script = mScripts.Get(cachePath)) {
838 script->mIsRunOnce = true;
839 script->MaybeDropStencil();
842 return;
845 // Don't bother trying to cache any URLs with cache-busting query
846 // parameters.
847 if (cachePath.FindChar('?') >= 0) {
848 return;
851 // Don't bother caching files that belong to the mochitest harness.
852 constexpr auto mochikitPrefix = "chrome://mochikit/"_ns;
853 if (StringHead(url, mochikitPrefix.Length()) == mochikitPrefix) {
854 return;
857 auto* script =
858 mScripts.GetOrInsertNew(cachePath, *this, url, cachePath, stencil);
859 if (isRunOnce) {
860 script->mIsRunOnce = true;
863 if (!script->MaybeDropStencil() && !script->mStencil) {
864 MOZ_ASSERT(stencil);
865 script->mStencil = stencil;
866 script->mReadyToExecute = true;
869 script->UpdateLoadTime(TimeStamp::Now());
870 script->mProcessTypes += CurrentProcessType();
873 void ScriptPreloader::NoteStencil(const nsCString& url,
874 const nsCString& cachePath,
875 ProcessType processType,
876 nsTArray<uint8_t>&& xdrData,
877 TimeStamp loadTime) {
878 // After data has been prepared, there's no point in noting further scripts,
879 // since the cache either has already been written, or is about to be
880 // written. Any time prior to the data being prepared, we can safely mutate
881 // mScripts without locking. After that point, the save thread is free to
882 // access it, and we can't alter it without locking.
883 if (mDataPrepared) {
884 return;
887 auto* script =
888 mScripts.GetOrInsertNew(cachePath, *this, url, cachePath, nullptr);
890 if (!script->HasRange()) {
891 MOZ_ASSERT(!script->HasArray());
893 script->mSize = xdrData.Length();
894 script->mXDRData.construct<nsTArray<uint8_t>>(
895 std::forward<nsTArray<uint8_t>>(xdrData));
897 auto& data = script->Array();
898 script->mXDRRange.emplace(data.Elements(), data.Length());
901 if (!script->mSize && !script->mStencil) {
902 // If the content process is sending us an entry for a stencil
903 // which was in the cache at startup, it expects us to already have this
904 // script data, so it doesn't send it.
906 // However, the cache may have been invalidated at this point (usually
907 // due to the add-on manager installing or uninstalling a legacy
908 // extension during very early startup), which means we may no longer
909 // have an entry for this script. Since that means we have no data to
910 // write to the new cache, and no JSScript to generate it from, we need
911 // to discard this entry.
912 mScripts.Remove(cachePath);
913 return;
916 script->UpdateLoadTime(loadTime);
917 script->mProcessTypes += processType;
920 /* static */
921 void ScriptPreloader::FillCompileOptionsForCachedStencil(
922 JS::CompileOptions& options) {
923 // Users of the cache do not require return values, so inform the JS parser in
924 // order for it to generate simpler bytecode.
925 options.setNoScriptRval(true);
927 // The ScriptPreloader trades off having bytecode available but not source
928 // text. This means the JS syntax-only parser is not used. If `toString` is
929 // called on functions in these scripts, the source-hook will fetch it over,
930 // so using `toString` of functions should be avoided in chrome js.
931 options.setSourceIsLazy(true);
934 /* static */
935 void ScriptPreloader::FillDecodeOptionsForCachedStencil(
936 JS::DecodeOptions& options) {
937 // ScriptPreloader's XDR buffer is alive during the Stencil is alive.
938 // The decoded stencil can borrow from it.
940 // NOTE: The XDR buffer is alive during the entire browser lifetime only
941 // when it's mmapped.
942 options.borrowBuffer = true;
945 already_AddRefed<JS::Stencil> ScriptPreloader::GetCachedStencil(
946 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
947 const nsCString& path) {
948 MOZ_RELEASE_ASSERT(
949 !(XRE_IsContentProcess() && !mCacheInitialized),
950 "ScriptPreloader must be initialized before getting cached "
951 "scripts in the content process.");
953 // If a script is used by both the parent and the child, it's stored only
954 // in the child cache.
955 if (mChildCache) {
956 RefPtr<JS::Stencil> stencil =
957 mChildCache->GetCachedStencilInternal(cx, options, path);
958 if (stencil) {
959 Telemetry::AccumulateCategorical(
960 Telemetry::LABELS_SCRIPT_PRELOADER_REQUESTS::HitChild);
961 return stencil.forget();
965 RefPtr<JS::Stencil> stencil = GetCachedStencilInternal(cx, options, path);
966 Telemetry::AccumulateCategorical(
967 stencil ? Telemetry::LABELS_SCRIPT_PRELOADER_REQUESTS::Hit
968 : Telemetry::LABELS_SCRIPT_PRELOADER_REQUESTS::Miss);
969 return stencil.forget();
972 already_AddRefed<JS::Stencil> ScriptPreloader::GetCachedStencilInternal(
973 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
974 const nsCString& path) {
975 auto* cachedScript = mScripts.Get(path);
976 if (cachedScript) {
977 return WaitForCachedStencil(cx, options, cachedScript);
979 return nullptr;
982 already_AddRefed<JS::Stencil> ScriptPreloader::WaitForCachedStencil(
983 JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
984 CachedStencil* script) {
985 if (!script->mReadyToExecute) {
986 // mReadyToExecute is kept as false only when off-thread decode task was
987 // available (pref is set to true) and the task was successfully created.
988 // See ScriptPreloader::StartDecodeTask methods.
989 MOZ_ASSERT(mDecodedStencils);
991 // Check for the finished operations that can contain our target.
992 if (mDecodedStencils->AvailableRead() > 0) {
993 FinishOffThreadDecode();
996 if (!script->mReadyToExecute) {
997 // Our target is not yet decoded.
999 // If script is small enough, we'd rather decode on main-thread than wait
1000 // for a decode task to complete.
1001 if (script->mSize < MAX_MAINTHREAD_DECODE_SIZE) {
1002 LOG(Info, "Script is small enough to recompile on main thread\n");
1004 script->mReadyToExecute = true;
1005 glean::script_preloader::mainthread_recompile.Add(1);
1006 } else {
1007 LOG(Info, "Must wait for async script load: %s\n", script->mURL.get());
1008 auto start = TimeStamp::Now();
1010 MonitorAutoLock mal(mMonitor);
1012 // Process finished tasks until our target is found.
1013 while (!script->mReadyToExecute) {
1014 if (mDecodedStencils->AvailableRead() > 0) {
1015 FinishOffThreadDecode();
1016 } else {
1017 MOZ_ASSERT(!mDecodingScripts.isEmpty());
1018 mWaitingForDecode = true;
1019 mal.Wait();
1020 mWaitingForDecode = false;
1024 double waitedMS = (TimeStamp::Now() - start).ToMilliseconds();
1025 Telemetry::Accumulate(Telemetry::SCRIPT_PRELOADER_WAIT_TIME,
1026 int(waitedMS));
1027 LOG(Debug, "Waited %fms\n", waitedMS);
1032 return script->GetStencil(cx, options);
1035 void ScriptPreloader::onDecodedStencilQueued() {
1036 mMonitor.AssertNotCurrentThreadOwns();
1037 MonitorAutoLock mal(mMonitor);
1039 if (mWaitingForDecode) {
1040 // Wake up the blocked main thread.
1041 mal.Notify();
1044 // NOTE: Do not perform DoFinishOffThreadDecode for partial data.
1047 void ScriptPreloader::OnDecodeTaskFinished() {
1048 mMonitor.AssertNotCurrentThreadOwns();
1049 MonitorAutoLock mal(mMonitor);
1051 if (mWaitingForDecode) {
1052 // Wake up the blocked main thread.
1053 mal.Notify();
1054 } else {
1055 // Issue a Runnable to handle all decoded stencils, even if the next
1056 // WaitForCachedStencil call has not happened yet.
1057 NS_DispatchToMainThread(
1058 NewRunnableMethod("ScriptPreloader::DoFinishOffThreadDecode", this,
1059 &ScriptPreloader::DoFinishOffThreadDecode));
1063 void ScriptPreloader::OnDecodeTaskFailed() {
1064 // NOTE: nullptr is enqueued to mDecodedStencils, and FinishOffThreadDecode
1065 // handles it as failure.
1066 OnDecodeTaskFinished();
1069 void ScriptPreloader::FinishPendingParses(MonitorAutoLock& aMal) {
1070 mMonitor.AssertCurrentThreadOwns();
1072 // If off-thread decoding task hasn't been started, nothing to do.
1073 // This can happen if the javascript.options.parallel_parsing pref was false,
1074 // or the decode task fails to start.
1075 if (!mDecodedStencils) {
1076 return;
1079 // Process any pending decodes that are in flight.
1080 while (!mDecodingScripts.isEmpty()) {
1081 if (mDecodedStencils->AvailableRead() > 0) {
1082 FinishOffThreadDecode();
1083 } else {
1084 mWaitingForDecode = true;
1085 aMal.Wait();
1086 mWaitingForDecode = false;
1091 void ScriptPreloader::DoFinishOffThreadDecode() {
1092 // NOTE: mDecodedStencils could already be reset.
1093 if (mDecodedStencils && mDecodedStencils->AvailableRead() > 0) {
1094 FinishOffThreadDecode();
1098 void ScriptPreloader::FinishOffThreadDecode() {
1099 MOZ_ASSERT(mDecodedStencils);
1101 while (mDecodedStencils->AvailableRead() > 0) {
1102 RefPtr<JS::Stencil> stencil;
1103 DebugOnly<int> reads = mDecodedStencils->Dequeue(&stencil, 1);
1104 MOZ_ASSERT(reads == 1);
1106 if (!stencil) {
1107 // DecodeTask failed.
1108 // Mark all remaining scripts to be decoded on the main thread.
1109 for (CachedStencil* next = mDecodingScripts.getFirst(); next;) {
1110 auto* script = next;
1111 next = script->getNext();
1113 script->mReadyToExecute = true;
1114 script->remove();
1117 break;
1120 CachedStencil* script = mDecodingScripts.getFirst();
1121 MOZ_ASSERT(script);
1123 LOG(Debug, "Finished off-thread decode of %s\n", script->mURL.get());
1124 script->mStencil = stencil.forget();
1125 script->mReadyToExecute = true;
1126 script->remove();
1129 if (mDecodingScripts.isEmpty()) {
1130 mDecodedStencils.reset();
1134 void ScriptPreloader::StartDecodeTask(JS::HandleObject scope) {
1135 auto start = TimeStamp::Now();
1136 LOG(Debug, "Off-thread decoding scripts...\n");
1138 Vector<JS::TranscodeSource> decodingSources;
1140 size_t size = 0;
1141 for (CachedStencil* next = mDecodingScripts.getFirst(); next;) {
1142 auto* script = next;
1143 next = script->getNext();
1145 MOZ_ASSERT(script->IsMemMapped());
1147 // Skip any scripts that we decoded on the main thread rather than
1148 // waiting for an off-thread operation to complete.
1149 if (script->mReadyToExecute) {
1150 script->remove();
1151 continue;
1153 if (!decodingSources.emplaceBack(script->Range(), script->mURL.get(), 0)) {
1154 break;
1157 LOG(Debug, "Beginning off-thread decode of script %s (%u bytes)\n",
1158 script->mURL.get(), script->mSize);
1160 size += script->mSize;
1163 MOZ_ASSERT(decodingSources.length() == mDecodingScripts.length());
1165 if (size == 0 && mDecodingScripts.isEmpty()) {
1166 return;
1169 AutoSafeJSAPI jsapi;
1170 JSContext* cx = jsapi.cx();
1171 JSAutoRealm ar(cx, scope ? scope : xpc::CompilationScope());
1173 JS::CompileOptions options(cx);
1174 FillCompileOptionsForCachedStencil(options);
1176 // All XDR buffers are mmapped and live longer than JS runtime.
1177 // The bytecode can be borrowed from the buffer.
1178 options.borrowBuffer = true;
1179 options.usePinnedBytecode = true;
1181 JS::DecodeOptions decodeOptions(options);
1183 size_t decodingSourcesLength = decodingSources.length();
1185 if (!StaticPrefs::javascript_options_parallel_parsing() ||
1186 !StartDecodeTask(decodeOptions, std::move(decodingSources))) {
1187 LOG(Info, "Can't decode %lu bytes of scripts off-thread",
1188 (unsigned long)size);
1189 for (auto* script : mDecodingScripts) {
1190 script->mReadyToExecute = true;
1192 return;
1195 LOG(Debug, "Initialized decoding of %u scripts (%u bytes) in %fms\n",
1196 (unsigned)decodingSourcesLength, (unsigned)size,
1197 (TimeStamp::Now() - start).ToMilliseconds());
1200 bool ScriptPreloader::StartDecodeTask(
1201 const JS::ReadOnlyDecodeOptions& decodeOptions,
1202 Vector<JS::TranscodeSource>&& decodingSources) {
1203 mDecodedStencils.emplace(decodingSources.length());
1204 MOZ_ASSERT(mDecodedStencils);
1206 nsCOMPtr<nsIRunnable> task =
1207 new DecodeTask(this, decodeOptions, std::move(decodingSources));
1209 nsresult rv = NS_DispatchBackgroundTask(task.forget());
1211 return NS_SUCCEEDED(rv);
1214 NS_IMETHODIMP ScriptPreloader::DecodeTask::Run() {
1215 auto failure = [&]() {
1216 RefPtr<JS::Stencil> stencil;
1217 DebugOnly<int> writes = mPreloader->mDecodedStencils->Enqueue(stencil);
1218 MOZ_ASSERT(writes == 1);
1219 mPreloader->OnDecodeTaskFailed();
1222 JS::FrontendContext* fc = JS::NewFrontendContext();
1223 if (!fc) {
1224 failure();
1225 return NS_OK;
1228 auto cleanup = MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
1230 size_t stackSize = TaskController::GetThreadStackSize();
1231 JS::SetNativeStackQuota(fc, JS::ThreadStackQuotaForSize(stackSize));
1233 size_t remaining = mDecodingSources.length();
1234 for (auto& source : mDecodingSources) {
1235 RefPtr<JS::Stencil> stencil;
1236 auto result = JS::DecodeStencil(fc, mDecodeOptions, source.range,
1237 getter_AddRefs(stencil));
1238 if (result != JS::TranscodeResult::Ok) {
1239 failure();
1240 return NS_OK;
1243 DebugOnly<int> writes = mPreloader->mDecodedStencils->Enqueue(stencil);
1244 MOZ_ASSERT(writes == 1);
1246 remaining--;
1247 if (remaining) {
1248 mPreloader->onDecodedStencilQueued();
1252 mPreloader->OnDecodeTaskFinished();
1253 return NS_OK;
1256 ScriptPreloader::CachedStencil::CachedStencil(ScriptPreloader& cache,
1257 InputBuffer& buf)
1258 : mCache(cache) {
1259 Code(buf);
1261 // Swap the mProcessTypes and mOriginalProcessTypes values, since we want to
1262 // start with an empty set of processes loaded into for this session, and
1263 // compare against last session's values later.
1264 mOriginalProcessTypes = mProcessTypes;
1265 mProcessTypes = {};
1268 bool ScriptPreloader::CachedStencil::XDREncode(JSContext* cx) {
1269 auto cleanup = MakeScopeExit([&]() { MaybeDropStencil(); });
1271 mXDRData.construct<JS::TranscodeBuffer>();
1273 JS::TranscodeResult code = JS::EncodeStencil(cx, mStencil, Buffer());
1274 if (code == JS::TranscodeResult::Ok) {
1275 mXDRRange.emplace(Buffer().begin(), Buffer().length());
1276 mSize = Range().length();
1277 return true;
1279 mXDRData.destroy();
1280 JS_ClearPendingException(cx);
1281 return false;
1284 already_AddRefed<JS::Stencil> ScriptPreloader::CachedStencil::GetStencil(
1285 JSContext* cx, const JS::ReadOnlyDecodeOptions& options) {
1286 MOZ_ASSERT(mReadyToExecute);
1287 if (mStencil) {
1288 return do_AddRef(mStencil);
1291 if (!HasRange()) {
1292 // We've already executed the script, and thrown it away. But it wasn't
1293 // in the cache at startup, so we don't have any data to decode. Give
1294 // up.
1295 return nullptr;
1298 // If we have no script at this point, the script was too small to decode
1299 // off-thread, or it was needed before the off-thread compilation was
1300 // finished, and is small enough to decode on the main thread rather than
1301 // wait for the off-thread decoding to finish. In either case, we decode
1302 // it synchronously the first time it's needed.
1304 auto start = TimeStamp::Now();
1305 LOG(Info, "Decoding stencil %s on main thread...\n", mURL.get());
1307 RefPtr<JS::Stencil> stencil;
1308 if (JS::DecodeStencil(cx, options, Range(), getter_AddRefs(stencil)) ==
1309 JS::TranscodeResult::Ok) {
1310 // Lock the monitor here to avoid data races on mScript
1311 // from other threads like the cache writing thread.
1313 // It is possible that we could end up decoding the same
1314 // script twice, because DecodeScript isn't being guarded
1315 // by the monitor; however, to encourage off-thread decode
1316 // to proceed for other scripts we don't hold the monitor
1317 // while doing main thread decode, merely while updating
1318 // mScript.
1319 mCache.mMonitor.AssertNotCurrentThreadOwns();
1320 MonitorAutoLock mal(mCache.mMonitor);
1322 mStencil = stencil.forget();
1324 if (mCache.mSaveComplete) {
1325 // We can only free XDR data if the stencil isn't borrowing data out of
1326 // it.
1327 if (!JS::StencilIsBorrowed(mStencil)) {
1328 FreeData();
1333 LOG(Debug, "Finished decoding in %fms",
1334 (TimeStamp::Now() - start).ToMilliseconds());
1336 return do_AddRef(mStencil);
1339 // nsIAsyncShutdownBlocker
1341 nsresult ScriptPreloader::GetName(nsAString& aName) {
1342 aName.AssignLiteral(u"ScriptPreloader: Saving bytecode cache");
1343 return NS_OK;
1346 nsresult ScriptPreloader::GetState(nsIPropertyBag** aState) {
1347 *aState = nullptr;
1348 return NS_OK;
1351 nsresult ScriptPreloader::BlockShutdown(
1352 nsIAsyncShutdownClient* aBarrierClient) {
1353 // If we're waiting on a timeout to finish saving, interrupt it and just save
1354 // immediately.
1355 mSaveMonitor.NotifyAll();
1356 return NS_OK;
1359 already_AddRefed<nsIAsyncShutdownClient> ScriptPreloader::GetShutdownBarrier() {
1360 nsCOMPtr<nsIAsyncShutdownService> svc = components::AsyncShutdown::Service();
1361 MOZ_RELEASE_ASSERT(svc);
1363 nsCOMPtr<nsIAsyncShutdownClient> barrier;
1364 Unused << svc->GetXpcomWillShutdown(getter_AddRefs(barrier));
1365 MOZ_RELEASE_ASSERT(barrier);
1367 return barrier.forget();
1370 NS_IMPL_ISUPPORTS(ScriptPreloader, nsIObserver, nsIRunnable, nsIMemoryReporter,
1371 nsINamed, nsIAsyncShutdownBlocker)
1373 #undef LOG
1375 } // namespace mozilla