Replace bare remote type strings in C++ process-selection plumbing with a RemoteType type which stores the parsed kind, isolation URI, and process selection attributes directly. This preserves the existing serialized string form for IPC and JS-facing APIs, while making native callers use explicit predicates and structured fields instead of manually parsing remote type prefixes and suffixes. No JS-exposed API for parsing or otherwise interpreting remote types are currently exposed in this patch. My current expectation is that this will likely look like a `nsIRemoteType` interface which wraps this `RemoteType` value type, exposing helpful getters for JS callers. Differential Revision: https://phabricator.services.mozilla.com/D310442
566 lines
19 KiB
C++
566 lines
19 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef ScriptPreloader_h
|
|
#define ScriptPreloader_h
|
|
|
|
#include "mozilla/EnumSet.h"
|
|
#include "mozilla/EventTargetAndLockCapability.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/loader/AutoMemMap.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/MaybeOneOf.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "mozilla/Monitor.h"
|
|
#include "mozilla/Range.h"
|
|
#include "mozilla/Result.h"
|
|
#include "mozilla/SPSCQueue.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/Vector.h"
|
|
|
|
#include <prio.h>
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsIAsyncShutdown.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIMemoryReporter.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIThread.h"
|
|
#include "nsITimer.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
#include "js/CompileOptions.h" // JS::DecodeOptions, JS::ReadOnlyDecodeOptions
|
|
#include "js/experimental/CompileScript.h" // JS::FrontendContext
|
|
#include "js/experimental/JSStencil.h" // JS::Stencil
|
|
#include "js/GCAnnotations.h" // for JS_HAZ_NON_GC_POINTER
|
|
#include "js/RootingAPI.h" // for Handle, Heap
|
|
#include "js/Transcoding.h" // for TranscodeBuffer, TranscodeRange, TranscodeSource
|
|
#include "js/TypeDecls.h" // for HandleObject, HandleScript
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
class ContentParent;
|
|
struct RemoteType;
|
|
} // namespace dom
|
|
namespace ipc {
|
|
class FileDescriptor;
|
|
}
|
|
namespace loader {
|
|
class InputBuffer;
|
|
class ScriptCacheChild;
|
|
|
|
enum class ProcessType : uint8_t {
|
|
Uninitialized,
|
|
Parent,
|
|
Web,
|
|
Extension,
|
|
PrivilegedAbout,
|
|
};
|
|
|
|
template <typename T>
|
|
struct Matcher {
|
|
virtual bool Matches(T) = 0;
|
|
};
|
|
} // namespace loader
|
|
|
|
using namespace mozilla::loader;
|
|
|
|
struct CachedStencilRefAndTime;
|
|
|
|
class ScriptPreloader : public nsIObserver,
|
|
public nsIMemoryReporter,
|
|
public nsIRunnable,
|
|
public nsINamed,
|
|
public nsIAsyncShutdownBlocker {
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
|
|
|
|
friend class mozilla::loader::ScriptCacheChild;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
NS_DECL_NSIMEMORYREPORTER
|
|
NS_DECL_NSIRUNNABLE
|
|
NS_DECL_NSINAMED
|
|
NS_DECL_NSIASYNCSHUTDOWNBLOCKER
|
|
|
|
private:
|
|
static StaticRefPtr<ScriptPreloader> gScriptPreloader;
|
|
static StaticRefPtr<ScriptPreloader> gChildScriptPreloader;
|
|
static StaticAutoPtr<AutoMemMap> gCacheData;
|
|
static StaticAutoPtr<AutoMemMap> gChildCacheData;
|
|
|
|
public:
|
|
static ScriptPreloader& GetSingleton();
|
|
static ScriptPreloader& GetChildSingleton();
|
|
|
|
static void DeleteSingleton();
|
|
static void DeleteCacheDataSingleton();
|
|
|
|
static ProcessType GetChildProcessType(const dom::RemoteType& remoteType);
|
|
|
|
// Fill some options that should be consistent across all scripts stored
|
|
// into preloader cache.
|
|
static void FillCompileOptionsForCachedStencil(JS::CompileOptions& options);
|
|
static void FillDecodeOptionsForCachedStencil(JS::DecodeOptions& options);
|
|
|
|
// Retrieves the stencil with the given cache key from the cache.
|
|
// Returns null if the stencil is not cached.
|
|
already_AddRefed<JS::Stencil> GetCachedStencil(
|
|
JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
|
|
const nsCString& path);
|
|
|
|
// Notes the execution of a script with the given URL and cache key.
|
|
// Depending on the stage of startup, the script may be serialized and
|
|
// stored to the startup script cache.
|
|
//
|
|
// If isRunOnce is true, this script is expected to run only once per
|
|
// process per browser session. A cached instance will not be kept alive
|
|
// for repeated execution.
|
|
void NoteStencil(const nsCString& url, const nsCString& cachePath,
|
|
JS::Stencil* stencil, bool isRunOnce = false);
|
|
|
|
// Notes the IPC arrival of the XDR data of a stencil compiled by some
|
|
// child process. See ScriptCacheChild::SendScriptsAndFinalize.
|
|
void NoteStencil(const nsCString& url, const nsCString& cachePath,
|
|
ProcessType processType, nsTArray<uint8_t>&& xdrData,
|
|
TimeStamp loadTime);
|
|
|
|
// Notes that we have received all script data of a child process with
|
|
// the given type. Must be called on the child preloader.
|
|
void NoteReceivedAllChildStencilsForProcess(ProcessType processType);
|
|
|
|
// Initializes the script cache from the startup script cache file.
|
|
Result<Ok, nsresult> InitCache(const nsAString& = u"scriptCache"_ns)
|
|
MOZ_REQUIRES(sMainThreadCapability);
|
|
|
|
Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile,
|
|
ScriptCacheChild* cacheChild)
|
|
MOZ_REQUIRES(sMainThreadCapability);
|
|
|
|
bool Active() const;
|
|
|
|
private:
|
|
Result<Ok, nsresult> InitCacheInternal(JS::Handle<JSObject*> scope = nullptr);
|
|
already_AddRefed<JS::Stencil> GetCachedStencilInternal(
|
|
JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
|
|
const nsCString& path);
|
|
|
|
public:
|
|
static ProcessType CurrentProcessType() {
|
|
MOZ_ASSERT(sProcessType != ProcessType::Uninitialized);
|
|
return sProcessType;
|
|
}
|
|
|
|
static void InitContentChild(dom::ContentParent& parent);
|
|
|
|
protected:
|
|
virtual ~ScriptPreloader();
|
|
|
|
private:
|
|
enum class ScriptStatus {
|
|
Restored,
|
|
Saved,
|
|
};
|
|
|
|
// Represents a cached script stencil, either initially read from the
|
|
// cache file, to be added to the next session's stencil cache file, or
|
|
// both.
|
|
//
|
|
// - Read from the cache, and being decoded off thread. In this case:
|
|
// - mReadyToExecute is false
|
|
// - mDecodingScripts contains the CachedStencil
|
|
// - mDecodedStencils have never contained the stencil
|
|
// - mStencil is null
|
|
//
|
|
// - Off-thread decode for the stencil has finished, but the stencil has not
|
|
// yet been dequeued nor executed. In this case:
|
|
// - mReadyToExecute is true
|
|
// - mDecodingScripts contains the CachedStencil
|
|
// - mDecodedStencils contains the decoded stencil
|
|
// - mStencil is null
|
|
//
|
|
// - Off-thread decode for the stencil has finished, and the stencil has
|
|
// been dequeued, but has not yet been executed. In this case:
|
|
// - mReadyToExecute is true
|
|
// - mDecodingScripts no longer contains the CachedStencil
|
|
// - mDecodedStencils no longer contains the decoded stencil
|
|
// - mStencil is non-null
|
|
//
|
|
// - Fully decoded, and ready to be added to the next session's cache
|
|
// file. In this case:
|
|
// - mReadyToExecute is true
|
|
// - mStencil is non-null
|
|
//
|
|
// A stencil to be added to the next session's cache file always has a
|
|
// non-null mStencil value. If it was read from the last session's cache
|
|
// file, it also has a non-empty mXDRRange range, which will be stored in
|
|
// the next session's cache file. If it was compiled in this session, its
|
|
// mXDRRange will initially be empty, and its mXDRData buffer will be
|
|
// populated just before it is written to the cache file.
|
|
class CachedStencil : public LinkedListElement<CachedStencil> {
|
|
public:
|
|
CachedStencil(CachedStencil&&) = delete;
|
|
|
|
CachedStencil(ScriptPreloader& cache, const nsCString& url,
|
|
const nsCString& cachePath, JS::Stencil* stencil)
|
|
: mCache(cache),
|
|
mURL(url),
|
|
mCachePath(cachePath),
|
|
mStencil(stencil),
|
|
mReadyToExecute(true),
|
|
mIsRunOnce(false) {}
|
|
|
|
inline CachedStencil(ScriptPreloader& cache, InputBuffer& buf);
|
|
|
|
~CachedStencil() = default;
|
|
|
|
ScriptStatus Status() const {
|
|
return mProcessTypes.isEmpty() ? ScriptStatus::Restored
|
|
: ScriptStatus::Saved;
|
|
}
|
|
|
|
struct StatusMatcher final : public Matcher<CachedStencil*> {
|
|
explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
|
|
|
|
virtual bool Matches(CachedStencil* script) override {
|
|
return script->Status() == mStatus;
|
|
}
|
|
|
|
const ScriptStatus mStatus;
|
|
};
|
|
|
|
void FreeData() {
|
|
// If the script data isn't mmapped, we need to release both it
|
|
// and the Range that points to it at the same time.
|
|
if (!IsMemMapped()) {
|
|
mXDRRange.reset();
|
|
mXDRData.destroy();
|
|
}
|
|
}
|
|
|
|
void UpdateLoadTime(const TimeStamp& loadTime) {
|
|
if (mLoadTime.IsNull() || loadTime < mLoadTime) {
|
|
mLoadTime = loadTime;
|
|
}
|
|
}
|
|
|
|
// Checks whether the cached JSScript for this entry will be needed
|
|
// again and, if not, drops it and returns true. This is the case for
|
|
// run-once scripts that do not still need to be encoded into the
|
|
// cache.
|
|
//
|
|
// If this method returns false, callers may set mScript to a cached
|
|
// JSScript instance for this entry. If it returns true, they should
|
|
// not.
|
|
bool MaybeDropStencil() {
|
|
if (mIsRunOnce && (HasRange() || !mCache.WillWriteScripts())) {
|
|
mStencil = nullptr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Encodes this script into XDR data, and stores the result in mXDRData.
|
|
// Returns true on success, false on failure.
|
|
bool XDREncode(JS::FrontendContext* cx);
|
|
|
|
// Encodes or decodes this script, in the storage format required by the
|
|
// script cache file.
|
|
template <typename Buffer>
|
|
void Code(Buffer& buffer) {
|
|
buffer.codeString(mURL);
|
|
buffer.codeString(mCachePath);
|
|
buffer.codeUint32(mOffset);
|
|
buffer.codeUint32(mSize);
|
|
buffer.codeUint8(mProcessTypes);
|
|
}
|
|
|
|
// Returns the XDR data generated for this script during this session. See
|
|
// mXDRData.
|
|
JS::TranscodeBuffer& Buffer() {
|
|
MOZ_ASSERT(HasBuffer());
|
|
return mXDRData.ref<JS::TranscodeBuffer>();
|
|
}
|
|
|
|
bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
|
|
|
|
// Returns the read-only XDR data for this script. See mXDRRange.
|
|
const JS::TranscodeRange& Range() {
|
|
MOZ_ASSERT(HasRange());
|
|
return mXDRRange.ref();
|
|
}
|
|
|
|
bool HasRange() { return mXDRRange.isSome(); }
|
|
|
|
bool IsMemMapped() const { return mXDRData.empty(); }
|
|
|
|
nsTArray<uint8_t>& Array() {
|
|
MOZ_ASSERT(HasArray());
|
|
return mXDRData.ref<nsTArray<uint8_t>>();
|
|
}
|
|
|
|
bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
|
|
|
|
already_AddRefed<JS::Stencil> GetStencil(
|
|
JSContext* cx, const JS::ReadOnlyDecodeOptions& options);
|
|
|
|
size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
|
|
auto size = mallocSizeOf(this);
|
|
|
|
if (HasArray()) {
|
|
size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
|
|
} else if (HasBuffer()) {
|
|
size += Buffer().sizeOfExcludingThis(mallocSizeOf);
|
|
}
|
|
|
|
if (mStencil) {
|
|
size += JS::SizeOfStencil(mStencil, mallocSizeOf);
|
|
}
|
|
|
|
// Note: mURL and mCachePath use the same string for scripts loaded
|
|
// by the message manager. The following statement avoids
|
|
// double-measuring in that case.
|
|
size += (mURL.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
|
|
mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
|
|
|
|
return size;
|
|
}
|
|
|
|
ScriptPreloader& mCache;
|
|
|
|
// The URL from which this script was initially read and compiled.
|
|
nsCString mURL;
|
|
// A unique identifier for this script's filesystem location, used as a
|
|
// primary cache lookup value.
|
|
nsCString mCachePath;
|
|
|
|
// The offset of this script in the cache file, from the start of the XDR
|
|
// data block.
|
|
uint32_t mOffset = 0;
|
|
// The size of this script's encoded XDR data.
|
|
uint32_t mSize = 0;
|
|
|
|
TimeStamp mLoadTime{};
|
|
|
|
RefPtr<JS::Stencil> mStencil;
|
|
|
|
// True if this script is ready to be executed. This means that either the
|
|
// off-thread portion of an off-thread decode has finished, or the
|
|
// off-thread decode failed, and may be immediately decoded
|
|
// whenever it is first executed.
|
|
bool mReadyToExecute = false;
|
|
|
|
// True if this script is expected to run once per process. If so, its
|
|
// JSScript instance will be dropped as soon as the script has
|
|
// executed and been encoded into the cache.
|
|
bool mIsRunOnce = false;
|
|
|
|
// The set of processes in which this script has been used.
|
|
EnumSet<ProcessType> mProcessTypes{};
|
|
|
|
// The set of processes which the script was loaded into during the
|
|
// last session, as read from the cache file.
|
|
EnumSet<ProcessType> mOriginalProcessTypes{};
|
|
|
|
// The read-only XDR data for this script, which was either read from an
|
|
// existing cache file, or generated by encoding a script which was
|
|
// compiled during this session.
|
|
Maybe<JS::TranscodeRange> mXDRRange;
|
|
|
|
// XDR data which was generated from a script compiled during this
|
|
// session, and will be written to the cache file.
|
|
//
|
|
// The format is JS::TranscodeBuffer if the script was XDR'd as part
|
|
// of this process, or nsTArray<> if the script was transfered by IPC
|
|
// from a child process.
|
|
MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
|
|
} JS_HAZ_NON_GC_POINTER;
|
|
|
|
friend struct CachedStencilRefAndTime;
|
|
|
|
template <ScriptStatus status>
|
|
static Matcher<CachedStencil*>* Match() {
|
|
static CachedStencil::StatusMatcher matcher{status};
|
|
return &matcher;
|
|
}
|
|
|
|
// The maximum size of scripts to re-decode on the main thread if off-thread
|
|
// decoding hasn't finished yet. In practice, we don't hit this very often,
|
|
// but when we do, re-decoding some smaller scripts on the main thread gives
|
|
// the background decoding a chance to catch up without blocking the main
|
|
// thread for quite as long.
|
|
static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
|
|
|
|
explicit ScriptPreloader(AutoMemMap* cacheData);
|
|
|
|
void Cleanup();
|
|
|
|
void FinishPendingParses(MonitorAutoLock& aMal);
|
|
void InvalidateCache() MOZ_REQUIRES(sMainThreadCapability);
|
|
|
|
// Opens the cache file for reading.
|
|
Result<Ok, nsresult> OpenCache();
|
|
|
|
// Writes a new cache file to disk. Must not be called on the main thread.
|
|
Result<Ok, nsresult> WriteCache() MOZ_REQUIRES(mSaveMonitor.Lock());
|
|
|
|
// Checks if everything's ready for cache writing, and calls StartCacheWrite
|
|
// if so. Can be called multiple times; won't do anything if a cache write has
|
|
// already been kicked off (or even finished).
|
|
void StartCacheWriteIfReady();
|
|
|
|
void StartCacheWrite();
|
|
|
|
// Prepares scripts for writing to the cache, serializing new scripts to
|
|
// XDR, and calculating their size-based offsets.
|
|
void PrepareCacheWrite();
|
|
|
|
void PrepareCacheWriteInternal();
|
|
|
|
void CacheWriteComplete();
|
|
|
|
void FinishContentStartup();
|
|
|
|
// Returns true if scripts added to the cache now will be encoded and
|
|
// written to the cache. If we've already encoded scripts for the cache
|
|
// write, or this is a content process which hasn't been asked to return
|
|
// script bytecode, this will return false.
|
|
bool WillWriteScripts();
|
|
|
|
// Returns a file pointer for the cache file with the given name in the
|
|
// current profile.
|
|
Result<nsCOMPtr<nsIFile>, nsresult> GetCacheFile(const nsAString& suffix);
|
|
|
|
// Waits for the given cached script to finish compiling off-thread, or
|
|
// decodes it synchronously on the main thread, as appropriate.
|
|
already_AddRefed<JS::Stencil> WaitForCachedStencil(
|
|
JSContext* cx, const JS::ReadOnlyDecodeOptions& options,
|
|
CachedStencil* script);
|
|
|
|
void StartDecodeTask(JS::Handle<JSObject*> scope);
|
|
|
|
private:
|
|
bool StartDecodeTask(const JS::ReadOnlyDecodeOptions& decodeOptions,
|
|
Vector<JS::TranscodeSource>&& decodingSources);
|
|
|
|
class DecodeTask : public Runnable {
|
|
ScriptPreloader* mPreloader;
|
|
JS::OwningDecodeOptions mDecodeOptions;
|
|
Vector<JS::TranscodeSource> mDecodingSources;
|
|
|
|
public:
|
|
DecodeTask(ScriptPreloader* preloader,
|
|
const JS::ReadOnlyDecodeOptions& decodeOptions,
|
|
Vector<JS::TranscodeSource>&& decodingSources)
|
|
: Runnable("ScriptPreloaderDecodeTask"),
|
|
mPreloader(preloader),
|
|
mDecodingSources(std::move(decodingSources)) {
|
|
mDecodeOptions.infallibleCopy(decodeOptions);
|
|
}
|
|
|
|
NS_IMETHOD Run() override;
|
|
};
|
|
|
|
friend class DecodeTask;
|
|
|
|
void onDecodedStencilQueued();
|
|
void OnDecodeTaskFinished();
|
|
void OnDecodeTaskFailed();
|
|
|
|
public:
|
|
void FinishOffThreadDecode();
|
|
void DoFinishOffThreadDecode();
|
|
|
|
already_AddRefed<nsIAsyncShutdownClient> GetShutdownBarrier();
|
|
|
|
size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
|
|
return (mallocSizeOf(this) +
|
|
mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
|
|
mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
|
|
}
|
|
|
|
using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedStencil>;
|
|
|
|
template <ScriptStatus status>
|
|
static size_t SizeOfHashEntries(ScriptHash& scripts,
|
|
mozilla::MallocSizeOf mallocSizeOf) {
|
|
size_t size = 0;
|
|
for (auto elem : IterHash(scripts, Match<status>())) {
|
|
size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
ScriptHash mScripts;
|
|
|
|
// True after we've shown the first window, and are no longer adding new
|
|
// scripts to the cache.
|
|
bool mStartupFinished = false;
|
|
|
|
// True once the startup sequence has reached CACHE_WRITE_TOPIC.
|
|
bool mStartupHasAdvancedToCacheWritingStage = false;
|
|
|
|
bool mCacheInitialized = false;
|
|
bool mSaveComplete = false;
|
|
bool mDataPrepared = false;
|
|
// May only be changed on the main thread, while `mSaveMonitor.Lock()` is
|
|
// held.
|
|
bool mCacheInvalidated MOZ_GUARDED_BY(mSaveMonitor) = false;
|
|
|
|
// The list of scripts currently being decoded in a background thread.
|
|
LinkedList<CachedStencil> mDecodingScripts;
|
|
|
|
// The result of the decode task.
|
|
//
|
|
// This is emplaced when starting the decode task, with the capacity equal
|
|
// to the number of sources.
|
|
//
|
|
// If the decode task failed, nullptr is enqueued.
|
|
Maybe<SPSCQueue<RefPtr<JS::Stencil>>> mDecodedStencils;
|
|
|
|
// True is main-thread is blocked and we should notify with Monitor. Access
|
|
// only while `mMonitor` is held.
|
|
bool mWaitingForDecode MOZ_GUARDED_BY(mMonitor) = false;
|
|
|
|
// The process type of the current process.
|
|
static ProcessType sProcessType;
|
|
|
|
// The process types we expect to see at some point during startup, and whose
|
|
// script data we want to wait for before kicking off the cache write.
|
|
EnumSet<ProcessType> mRequiredChildProcessStencils;
|
|
|
|
// The process types for which remote processes have been initialized, and
|
|
// are expected to send back script data. Only used in the *child cache* in
|
|
// the parent process.
|
|
EnumSet<ProcessType> mRequestedChildProcessStencils;
|
|
|
|
// The process types from which we have received script data.
|
|
EnumSet<ProcessType> mReceivedChildProcessStencils;
|
|
|
|
RefPtr<ScriptPreloader> mChildCache;
|
|
ScriptCacheChild* mChildActor = nullptr;
|
|
|
|
nsString mBaseName;
|
|
nsCString mContentStartupFinishedTopic;
|
|
|
|
nsCOMPtr<nsIFile> mProfD;
|
|
nsCOMPtr<nsIThread> mSaveThread;
|
|
nsCOMPtr<nsITimer> mSaveTimer;
|
|
|
|
// The mmapped cache data from this session's cache file.
|
|
// The instance is held by either `gCacheData` or `gChildCacheData` static
|
|
// fields, and its lifetime is guaranteed to be longer than ScriptPreloader
|
|
// instance.
|
|
AutoMemMap* mCacheData;
|
|
|
|
Monitor mMonitor MOZ_ACQUIRED_AFTER(mSaveMonitor.Lock());
|
|
MainThreadAndLockCapability<Monitor> mSaveMonitor;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // ScriptPreloader_h
|