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"
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"
42 #include "nsDirectoryServiceUtils.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;
66 static LazyLogModule
gLog("ScriptPreloader");
68 #define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
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
;
79 ProcessType
ScriptPreloader::sProcessType
;
81 nsresult
ScriptPreloader::CollectReports(nsIHandleReportCallback
* aHandleReport
,
82 nsISupports
* aData
, bool aAnonymize
) {
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.");
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.");
110 MOZ_COLLECT_REPORT("script-preloader-memmapped-cache", KIND_NONHEAP
,
111 UNITS_BYTES
, mCacheData
->nonHeapSizeOfExcludingThis(),
112 "The memory-mapped startup script cache file.");
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();
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
;
174 void ScriptPreloader::DeleteSingleton() {
175 gScriptPreloader
= nullptr;
176 gChildScriptPreloader
= nullptr;
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
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
213 if (fd
.IsValid() && !cache
.mCacheInvalidated
) {
214 Unused
<< parent
.SendPScriptCacheConstructor(fd
, wantScriptData
);
216 Unused
<< parent
.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND
,
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() {
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__
,
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
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
);
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
294 if (mSaveComplete
&& !mSaveThread
&& mChildCache
) {
295 mSaveComplete
= false;
302 MonitorSingleWriterAutoLock
saveMonitorAutoLock(mSaveMonitor
);
304 mCacheInvalidated
= true;
307 // If we're waiting on a timeout to finish saving, interrupt it and just save
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
) {
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")) {
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
)) {
359 void ScriptPreloader::FinishContentStartup() {
360 MOZ_ASSERT(XRE_IsContentProcess());
363 if (mContentStartupFinishedTopic
.Equals(CONTENT_DOCUMENT_LOADED_TOPIC
)) {
364 MOZ_ASSERT(sProcessType
== ProcessType::PrivilegedAbout
);
366 MOZ_ASSERT(sProcessType
!= ProcessType::PrivilegedAbout
);
370 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
371 obs
->RemoveObserver(this, mContentStartupFinishedTopic
.get());
373 mSaveTimer
= nullptr;
375 mStartupFinished
= true;
378 mChildActor
->SendScriptsAndFinalize(mScripts
);
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);
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
));
427 MOZ_TRY(cacheFile
->Exists(&exists
));
429 MOZ_TRY(cacheFile
->MoveTo(nullptr, mBaseName
+ u
"-current.bin"_ns
));
431 MOZ_TRY(cacheFile
->SetLeafName(mBaseName
+ u
"-current.bin"_ns
));
432 MOZ_TRY(cacheFile
->Exists(&exists
));
434 return Err(NS_ERROR_FILE_NOT_FOUND
);
438 MOZ_TRY(mCacheData
->init(cacheFile
));
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()) {
456 // Grab the compilation scope before initializing the URLPreloader, since
457 // it's not safe to run component loader code during its critical section.
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
;
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
);
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.
507 NS_NewTimerWithObserver(getter_AddRefs(mSaveTimer
), this,
508 CHILD_STARTUP_TIMEOUT_MS
,
509 nsITimer::TYPE_ONE_SHOT
);
513 if (cacheFile
.isNothing()) {
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();
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());
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
);
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
);
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());
598 script
->mReadyToExecute
= true;
601 const auto& cachePath
= script
->mCachePath
;
602 mScripts
.InsertOrUpdate(cachePath
, std::move(script
));
606 return Err(NS_ERROR_UNEXPECTED
);
609 mDecodingScripts
= std::move(scripts
);
613 StartDecodeTask(scope
);
617 void ScriptPreloader::PrepareCacheWriteInternal() {
618 MOZ_ASSERT(NS_IsMainThread());
620 mMonitor
.AssertCurrentThreadOwns();
622 auto cleanup
= MakeScopeExit([&]() {
624 mChildCache
->PrepareCacheWrite();
633 JSAutoRealm
ar(jsapi
.cx(), xpc::PrivilegedJunkScope());
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
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
;
648 if (!(script
->mProcessTypes
== script
->mOriginalProcessTypes
)) {
649 // Note: EnumSet doesn't support operator!=, hence the weird form above.
653 if (!script
->mSize
&& !script
->XDREncode(jsapi
.cx())) {
659 mSaveComplete
= true;
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.
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
));
702 // If we don't have anything we need to save, we're done.
706 nsCOMPtr
<nsIFile
> cacheFile
;
707 MOZ_TRY_VAR(cacheFile
, GetCacheFile(u
"-new.bin"_ns
));
710 MOZ_TRY(cacheFile
->Exists(&exists
));
712 MOZ_TRY(cacheFile
->Remove(false));
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());
738 for (auto script
: scripts
) {
739 script
->mOffset
= offset
;
740 MOZ_DIAGNOSTIC_ASSERT(
741 JS::IsTranscodingBytecodeOffsetAligned(script
->mOffset
));
744 offset
+= script
->mSize
;
745 MOZ_DIAGNOSTIC_ASSERT(
746 JS::IsTranscodingBytecodeOffsetAligned(script
->mSize
));
749 uint8_t headerSize
[4];
750 LittleEndian::writeUint32(headerSize
, buf
.cursor());
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
;
764 MOZ_TRY(WritePadding(fd
, 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
775 if (script
->mStencil
&& !JS::StencilIsBorrowed(script
->mStencil
)) {
781 MOZ_TRY(cacheFile
->MoveTo(nullptr, mBaseName
+ u
".bin"_ns
));
786 nsresult
ScriptPreloader::GetName(nsACString
& aName
) {
787 aName
.AssignLiteral("ScriptPreloader");
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
),
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
) {
837 if (auto script
= mScripts
.Get(cachePath
)) {
838 script
->mIsRunOnce
= true;
839 script
->MaybeDropStencil();
845 // Don't bother trying to cache any URLs with cache-busting query
847 if (cachePath
.FindChar('?') >= 0) {
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
) {
858 mScripts
.GetOrInsertNew(cachePath
, *this, url
, cachePath
, stencil
);
860 script
->mIsRunOnce
= true;
863 if (!script
->MaybeDropStencil() && !script
->mStencil
) {
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.
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
);
916 script
->UpdateLoadTime(loadTime
);
917 script
->mProcessTypes
+= processType
;
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);
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
) {
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.
956 RefPtr
<JS::Stencil
> stencil
=
957 mChildCache
->GetCachedStencilInternal(cx
, options
, path
);
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
);
977 return WaitForCachedStencil(cx
, options
, cachedScript
);
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);
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();
1017 MOZ_ASSERT(!mDecodingScripts
.isEmpty());
1018 mWaitingForDecode
= true;
1020 mWaitingForDecode
= false;
1024 double waitedMS
= (TimeStamp::Now() - start
).ToMilliseconds();
1025 Telemetry::Accumulate(Telemetry::SCRIPT_PRELOADER_WAIT_TIME
,
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.
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.
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
) {
1079 // Process any pending decodes that are in flight.
1080 while (!mDecodingScripts
.isEmpty()) {
1081 if (mDecodedStencils
->AvailableRead() > 0) {
1082 FinishOffThreadDecode();
1084 mWaitingForDecode
= true;
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);
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;
1120 CachedStencil
* script
= mDecodingScripts
.getFirst();
1123 LOG(Debug
, "Finished off-thread decode of %s\n", script
->mURL
.get());
1124 script
->mStencil
= stencil
.forget();
1125 script
->mReadyToExecute
= true;
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
;
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
) {
1153 if (!decodingSources
.emplaceBack(script
->Range(), script
->mURL
.get(), 0)) {
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()) {
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;
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();
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
) {
1243 DebugOnly
<int> writes
= mPreloader
->mDecodedStencils
->Enqueue(stencil
);
1244 MOZ_ASSERT(writes
== 1);
1248 mPreloader
->onDecodedStencilQueued();
1252 mPreloader
->OnDecodeTaskFinished();
1256 ScriptPreloader::CachedStencil::CachedStencil(ScriptPreloader
& cache
,
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
;
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();
1280 JS_ClearPendingException(cx
);
1284 already_AddRefed
<JS::Stencil
> ScriptPreloader::CachedStencil::GetStencil(
1285 JSContext
* cx
, const JS::ReadOnlyDecodeOptions
& options
) {
1286 MOZ_ASSERT(mReadyToExecute
);
1288 return do_AddRef(mStencil
);
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
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
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
1327 if (!JS::StencilIsBorrowed(mStencil
)) {
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");
1346 nsresult
ScriptPreloader::GetState(nsIPropertyBag
** aState
) {
1351 nsresult
ScriptPreloader::BlockShutdown(
1352 nsIAsyncShutdownClient
* aBarrierClient
) {
1353 // If we're waiting on a timeout to finish saving, interrupt it and just save
1355 mSaveMonitor
.NotifyAll();
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
)
1375 } // namespace mozilla