/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
// *Some* NativeSets are referenced from mClassInfo2NativeSetMap. // *All* NativeSets are referenced from mNativeSetMap. // So, in mClassInfo2NativeSetMap we just clear references to the unmarked. // In mNativeSetMap we clear the references to the unmarked *and* delete them.
class AsyncFreeSnowWhite : public Runnable { public:
NS_IMETHOD Run() override {
AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
AUTO_PROFILER_LABEL("AsyncFreeSnowWhite::Run", GCCC_FreeSnowWhite);
auto timerId = glean::cycle_collector::async_snow_white_freeing.Start(); // 2 ms budget, given that kICCSliceBudget is only 3 ms
SliceBudget budget = SliceBudget(TimeBudget(2)); bool hadSnowWhiteObjects =
nsCycleCollector_doDeferredDeletionWithBudget(budget);
glean::cycle_collector::async_snow_white_freeing.StopAndAccumulate(
std::move(timerId)); if (hadSnowWhiteObjects && !mContinuation) {
mContinuation = true; if (NS_FAILED(Dispatch())) {
mActive = false;
}
} else {
mActive = false;
} return NS_OK;
}
void CompartmentPrivate::SystemIsBeingShutDown() { // We may call this multiple times when the compartment contains more than one // realm. if (!wasShutdown) {
mWrappedJSMap->ShutdownMarker();
wasShutdown = true;
}
}
// Create the realm private.
RealmPrivate* realmPriv = new RealmPrivate(realm);
MOZ_ASSERT(!GetRealmPrivate(realm));
SetRealmPrivate(realm, realmPriv);
nsIPrincipal* principal = GetRealmPrincipal(realm);
Compartment* c = JS::GetCompartment(aGlobal);
// Create the compartment private if needed. if (CompartmentPrivate* priv = CompartmentPrivate::Get(c)) {
MOZ_ASSERT(priv->originInfo.IsSameOrigin(principal));
} else { auto scope = mozilla::MakeUnique<XPCWrappedNativeScope>(c, aGlobal);
priv = new CompartmentPrivate(c, std::move(scope),
BasePrincipal::Cast(principal), aSite);
JS_SetCompartmentPrivate(c, priv);
}
}
// As XPCJSRuntime can live longer than when we shutdown the observer service, // we have our own getter to account for this. static nsCOMPtr<nsIObserverService> GetObserverService() { if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { return nullptr;
} return mozilla::services::GetObserverService();
}
staticbool TryParseLocationURICandidate( const nsACString& uristr, RealmPrivate::LocationHint aLocationHint,
nsIURI** aURI) { static constexpr auto kGRE = "resource://gre/"_ns; static constexpr auto kToolkit = "chrome://global/"_ns; static constexpr auto kBrowser = "chrome://browser/"_ns;
if (aLocationHint == RealmPrivate::LocationHintAddon) { // Blacklist some known locations which are clearly not add-on related. if (StringBeginsWith(uristr, kGRE) || StringBeginsWith(uristr, kToolkit) ||
StringBeginsWith(uristr, kBrowser)) { returnfalse;
}
// -- GROSS HACK ALERT -- // The Yandex Elements 8.10.2 extension implements its own "xb://" URL // scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up // calling into the extension's own JS-implemented nsIProtocolHandler // object, which we can't allow while we're iterating over the JS heap. // So just skip any such URL. // -- GROSS HACK ALERT -- if (StringBeginsWith(uristr, "xb"_ns)) { returnfalse;
}
}
nsCOMPtr<nsIURI> uri; if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) { returnfalse;
}
nsAutoCString scheme; if (NS_FAILED(uri->GetScheme(scheme))) { returnfalse;
}
// Cannot really map data: and blob:. // Also, data: URIs are pretty memory hungry, which is kinda bad // for memory reporter use. if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) { returnfalse;
}
// Need to parse the URI. if (location.IsEmpty()) { returnfalse;
}
// Handle Sandbox location strings. // A sandbox string looks like this, for anonymous sandboxes, and builds // where Sandbox location tagging is enabled: // // <sandboxName> (from: <js-stack-frame-filename>:<lineno>) // // where <sandboxName> is user-provided via Cu.Sandbox() // and <js-stack-frame-filename> and <lineno> is the stack frame location // from where Cu.Sandbox was called. // // Otherwise, it is simply the caller-provided name, which is usually a URI. // // <js-stack-frame-filename> furthermore is "free form", often using a // "uri -> uri -> ..." chain. The following code will and must handle this // common case. // // It should be noted that other parts of the code may already rely on the // "format" of these strings.
// When parsing we're looking for the right-most URI. This URI may be in // <sandboxName>, so we try this first. if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint,
aURI)) { returntrue;
}
// Not in <sandboxName> so we need to inspect <js-stack-frame-filename> and // the chain that is potentially contained within and grab the rightmost // item that is actually a URI.
// First, hack off the :<lineno>) part as well
int32_t ridx = location.RFind(":"_ns);
nsAutoCString chain(
Substring(location, idx + fromLength, ridx - idx - fromLength));
// Loop over the "->" chain. This loop also works for non-chains, or more // correctly chains with only one item. for (;;) {
idx = chain.RFind(arrow); if (idx < 0) { // This is the last chain item. Try to parse what is left. return TryParseLocationURICandidate(chain, aLocationHint, aURI);
}
// Try to parse current chain item if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength),
aLocationHint, aURI)) { returntrue;
}
// Current chain item couldn't be parsed. // Strip current item and continue.
chain = Substring(chain, 0, idx);
}
MOZ_CRASH("Chain parser loop does not terminate");
}
staticbool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) { // System principal gets a free pass. if (aPrincipal->IsSystemPrincipal()) { returntrue;
}
auto* principal = BasePrincipal::Cast(aPrincipal);
// ExpandedPrincipal gets a free pass. if (principal->Is<ExpandedPrincipal>()) { returntrue;
}
// WebExtension principals get a free pass. if (principal->AddonPolicy()) { returntrue;
}
// pdf.js is a special-case too. if (nsContentUtils::IsPDFJS(principal)) { returntrue;
}
// Check whether our URI is an "about:" URI that allows scripts. If it is, // we need to allow JS to run. if (aPrincipal->SchemeIs("about")) {
uint32_t flags;
nsresult rv = aPrincipal->GetAboutModuleFlags(&flags); if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) { returntrue;
}
}
mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); if (mImmuneToScriptPolicy) { return;
} // If we're not immune, we should have a real principal with a URI. // Check the principal against the new-style domain policy. bool policyAllows;
nsresult rv = prin->GetIsScriptAllowedByPolicy(&policyAllows); if (NS_SUCCEEDED(rv)) {
mScriptBlockedByPolicy = !policyAllows; return;
} // Something went wrong - be safe and block script.
mScriptBlockedByPolicy = true;
}
bool CompartmentOriginInfo::MightBeWebContent() const { // Compartments with principals that are either the system principal or an // expanded principal are definitely not web content. return !nsContentUtils::IsSystemOrExpandedPrincipal(mOrigin);
}
void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment) { // Note: we call this for all compartments that contain realms with a // particular principal. Not all of these compartments have a // CompartmentPrivate (for instance the temporary compartment/realm // created by the JS engine for off-thread parsing). if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
priv->originInfo.SetChangedDocumentDomain();
}
}
// Sandbox can't be a Proxy so it must have a static prototype.
JSObject* proto = GetStaticPrototype(aObj); if (!proto || !IsSandboxPrototypeProxy(proto)) { return nullptr;
}
// Nukes all wrappers into or out of the given realm, and prevents new // wrappers from being created. Additionally marks the realm as // unscriptable after wrappers have been nuked. // // Note: This should *only* be called for browser or extension realms. // Wrappers between web compartments must never be cut in web-observable // ways. void NukeAllWrappersForRealm(
JSContext* cx, JS::Realm* realm,
js::NukeReferencesToWindow nukeReferencesToWindow) { // We do the following: // * Nuke all wrappers into the realm. // * Nuke all wrappers out of the realm's compartment, once we have nuked all // realms in it.
js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm,
nukeReferencesToWindow,
js::NukeAllReferences);
// Mark the realm as unscriptable.
xpc::RealmPrivate::Get(realm)->scriptability.Block();
}
} // namespace xpc
staticvoid CompartmentDestroyedCallback(JS::GCContext* gcx,
JS::Compartment* compartment) { // NB - This callback may be called in JS_DestroyContext, which happens // after the XPCJSRuntime has been torn down.
// Get the current compartment private into a UniquePtr (which will do the // cleanup for us), and null out the private (which may already be null).
mozilla::UniquePtr<CompartmentPrivate> priv(
CompartmentPrivate::Get(compartment));
JS_SetCompartmentPrivate(compartment, nullptr);
}
/* * Return true if there exists a non-system inner window which is a current * inner window and whose reflector is gray. We don't merge system * compartments, so we don't use them to trigger merging CCs.
*/ bool XPCJSRuntime::UsefulToMergeZones() const {
MOZ_ASSERT(NS_IsMainThread());
// Turns out, actually making this return true often enough makes Windows // mochitest-gl OOM a lot. Need to figure out what's going on there; see // bug 1277036.
if (self->mPrevGCSliceCallback) {
(*self->mPrevGCSliceCallback)(cx, progress, desc);
}
}
/* static */ void XPCJSRuntime::DoCycleCollectionCallback(JSContext* cx) { // The GC has detected that a CC at this point would collect a tremendous // amount of garbage that is being revivified unnecessarily. // // The GC_WAITING reason is a little overloaded here, but we want to do // a CC to allow Realms to be collected when they are referenced by a cycle.
NS_DispatchToCurrentThread(NS_NewRunnableFunction( "XPCJSRuntime::DoCycleCollectionCallback",
[]() { nsJSContext::CycleCollectNow(CCReason::GC_WAITING, nullptr); }));
XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); if (!self) { return;
}
if (self->mPrevDoCycleCollectionCallback) {
(*self->mPrevDoCycleCollectionCallback)(cx);
}
}
void XPCJSRuntime::CustomGCCallback(JSGCStatus status) {
nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone()); for (uint32_t i = 0; i < callbacks.Length(); ++i) {
callbacks[i](status);
}
}
if (CycleCollectedJSContext* ccx = self->GetContext()) { constauto* cx = static_cast<const XPCJSContext*>(ccx); if (AutoMarkingPtr* roots = cx->mAutoRoots) {
roots->MarkAfterJSFinalizeAll();
}
// Now we are going to recycle any unused WrappedNativeTearoffs. // We do this by iterating all the live callcontexts // and marking the tearoffs in use. And then we // iterate over all the WrappedNative wrappers and sweep their // tearoffs. // // This allows us to perhaps minimize the growth of the // tearoffs. And also makes us not hold references to interfaces // on our wrapped natives that we are not actually using. // // XXX We may decide to not do this on *every* gc cycle.
XPCCallContext* ccxp = cx->GetCallContext(); while (ccxp) { // Deal with the strictness of callcontext that // complains if you ask for a tearoff when // it is in a state where the tearoff could not // possibly be valid. if (ccxp->CanGetTearOff()) {
XPCWrappedNativeTearOff* to = ccxp->GetTearOff(); if (to) {
to->Mark();
}
}
ccxp = ccxp->GetPrevCallContext();
}
}
// Now we need to kill the 'Dying' XPCWrappedNativeProtos. // // We transferred these native objects to this list when their JSObjects // were finalized. We did not destroy them immediately at that point // because the ordering of JS finalization is not deterministic and we did // not yet know if any wrappers that might still be referencing the protos // were still yet to be finalized and destroyed. We *do* know that the // protos' JSObjects would not have been finalized if there were any // wrappers that referenced the proto but were not themselves slated for // finalization in this gc cycle. // // At this point we know that any and all wrappers that might have been // referencing the protos in the dying list are themselves dead. So, we // can safely delete all the protos in the list.
self->mDyingWrappedNativeProtos.clear();
/* static */ void XPCJSRuntime::WeakPointerZonesCallback(JSTracer* trc, void* data) { // Called before each sweeping slice -- after processing any final marking // triggered by barriers -- to clear out any references to things that are // about to be finalized and update any pointers to moved GC things.
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
// This callback is always called from within the GC so set the mGCIsRunning // flag to prevent AssertInvalidWrappedJSNotInTable from trying to call back // into the JS API. This has often already been set by FinalizeCallback by the // time we get here, but it may not be if we are doing a shutdown GC or if we // are called for compacting GC.
AutoRestore<bool> restoreState(self->mGCIsRunning);
self->mGCIsRunning = true;
/* static */ void XPCJSRuntime::WeakPointerCompartmentCallback(JSTracer* trc,
JS::Compartment* comp, void* data) { // Called immediately after the ZoneGroup weak pointer callback, but only // once for each compartment that is being swept.
CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp); if (xpcComp) {
xpcComp->UpdateWeakPointersAfterGC(trc);
}
}
MutexAutoLock lock(mMutex); while (mWaiting) {
mCondVar.Wait();
}
}
};
staticvoid OnLargeAllocationFailureCallback() { // This callback can be called from any thread, including internal JS helper // and DOM worker threads. We need to send the low-memory event via the // observer service which can only be called on the main thread, so proxy to // the main thread if we're not there already. The purpose of this callback // is to synchronously free some memory so the caller can retry a failed // allocation, so block on the completion.
if (NS_IsMainThread()) {
XPCJSRuntime::Get()->OnLargeAllocationFailure(); return;
}
RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable; if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) { return;
}
r->BlockUntilDone();
}
// Usually this is used through nsIPlatformInfo. However, being able to query // this interface on all threads risk triggering some main-thread assertions // which is not guaranteed by the callers of GetBuildId. externconstchar gToolkitBuildID[];
size_t XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
size_t n = 0;
n += mallocSizeOf(this);
n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf);
n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf);
n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf);
n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf);
// There are other XPCJSRuntime members that could be measured; the above // ones have been seen by DMD to be worth measuring. More stuff may be // added later.
return n;
}
size_t CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) {
size_t n = mallocSizeOf(this);
n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf); return n;
}
void XPCJSRuntime::Shutdown(JSContext* cx) { // This destructor runs before ~CycleCollectedJSContext, which does the actual // JS_DestroyContext() call. But destroying the context triggers one final GC, // which can call back into the context with various callbacks if we aren't // careful. Remove the relevant callbacks, but leave the weak pointer // callbacks to clear out any remaining table entries.
JS_RemoveFinalizeCallback(cx, FinalizeCallback);
xpc_DelocalizeRuntime(JS_GetRuntime(cx));
JS::SetGCSliceCallback(cx, mPrevGCSliceCallback);
nsScriptSecurityManager::ClearJSCallbacks(cx);
// Clean up and destroy maps. Any remaining entries in mWrappedJSMap will be // cleaned up by the weak pointer callbacks.
mIID2NativeInterfaceMap = nullptr;
mClassInfo2NativeSetMap = nullptr;
mNativeSetMap = nullptr;
// Prevent ~LinkedList assertion failures if we leaked things.
mWrappedNativeScopes.clear();
// If |*anonymizeID| is non-zero and this is a user realm, the name will // be anonymized. staticvoid GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID, bool replaceSlashes) { if (*anonymizeID && !js::IsSystemRealm(realm)) {
name.AppendPrintf("<anonymized-%d>", *anonymizeID);
*anonymizeID += 1;
} elseif (JSPrincipals* principals = JS::GetRealmPrincipals(realm)) {
nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name); if (NS_FAILED(rv)) {
name.AssignLiteral("(unknown)");
}
// If the realm's location (name) differs from the principal's script // location, append the realm's location to allow differentiation of // multiple realms owned by the same principal (e.g. components owned // by the system or null principal).
RealmPrivate* realmPrivate = RealmPrivate::Get(realm); if (realmPrivate) { const nsACString& location = realmPrivate->GetLocation(); if (!location.IsEmpty() && !location.Equals(name)) {
name.AppendLiteral(", ");
name.Append(location);
}
}
if (*anonymizeID) { // We might have a file:// URL that includes a path from the local // filesystem, which should be omitted if we're anonymizing. staticconstchar* filePrefix = "file://"; int filePos = name.Find(filePrefix); if (filePos >= 0) { int pathPos = filePos + strlen(filePrefix); int lastSlashPos = -1; for (int i = pathPos; i < int(name.Length()); i++) { if (name[i] == '/' || name[i] == '\\') {
lastSlashPos = i;
}
} if (lastSlashPos != -1) {
name.ReplaceLiteral(pathPos, lastSlashPos - pathPos, "<anonymized>");
} else { // Something went wrong. Anonymize the entire path to be // safe.
name.Truncate(pathPos);
name += "<anonymized?!>";
}
}
// We might have a location like this: // inProcessBrowserChildGlobal?ownedBy=http://www.example.com/ // The owner should be omitted if it's not a chrome: URI and we're // anonymizing. staticconstchar* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy="; int ownedByPos = name.Find(ownedByPrefix); if (ownedByPos >= 0) { constchar* chrome = "chrome:"; int ownerPos = ownedByPos + strlen(ownedByPrefix); const nsDependentCSubstring& ownerFirstPart =
Substring(name, ownerPos, strlen(chrome)); if (!ownerFirstPart.EqualsASCII(chrome)) {
name.Truncate(ownerPos);
name += "<anonymized>";
}
}
}
// A hack: replace forward slashes with '\\' so they aren't // treated as path separators. Users of the reporters // (such as about:memory) have to undo this change. if (replaceSlashes) {
name.ReplaceChar('/', '\\');
}
} else {
name.AssignLiteral("null-principal");
}
}
class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter {
~JSMainRuntimeTemporaryPeakReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override {
MOZ_COLLECT_REPORT( "js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES,
JSMainRuntimeTemporaryPeakDistinguishedAmount(), "Peak transient data size in the main JSRuntime (the current size " "of which is reported as " "'explicit/js-non-window/runtime/temporary').");
// The REPORT* macros do an unconditional report. The ZRREPORT* macros are for // realms and zones; they aggregate any entries smaller than // SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap" // entries for the realm.
ZRREPORT_BYTES(pathPrefix + "compartments/compartment-objects"_ns,
zStats.compartmentObjects, "The JS::Compartment objects in this zone.");
ZRREPORT_BYTES(
pathPrefix + "compartments/cross-compartment-wrapper-tables"_ns,
zStats.crossCompartmentWrappersTables, "The cross-compartment wrapper tables.");
ZRREPORT_BYTES(
pathPrefix + "compartments/private-data"_ns,
zStats.compartmentsPrivateData, "Extra data attached to each compartment by XPConnect, including " "its wrapped-js.");
// zStats.smallBuffersGCHeap is not reported as a separate item here as it's // reported as part of the owning cell. We must still count it as part of the // total heap size.
gcTotal += zStats.smallBuffersGCHeap;
ZRREPORT_BYTES(pathPrefix + "zone-object"_ns, zStats.zoneObject, "The JS::Zone object itself.");
ZRREPORT_BYTES(pathPrefix + "regexp-zone"_ns, zStats.regexpZone, "The regexp zone and regexp data.");
ZRREPORT_BYTES(pathPrefix + "jit-zone"_ns, zStats.jitZone, "The JIT zone.");
ZRREPORT_BYTES(pathPrefix + "cacheir-stubs"_ns, zStats.cacheIRStubs, "The JIT's IC stubs (excluding code).");
ZRREPORT_BYTES(pathPrefix + "script-counts-map"_ns, zStats.scriptCountsMap, "Profiling-related information for scripts.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/ion"_ns, zStats.code.ion, "Code generated by the IonMonkey JIT.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/baseline"_ns, zStats.code.baseline, "Code generated by the Baseline JIT.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/regexp"_ns, zStats.code.regexp, "Code generated by the regexp JIT.");
ZRREPORT_NONHEAP_BYTES(
pathPrefix + "code/other"_ns, zStats.code.other, "Code generated by the JITs for wrappers and trampolines.");
ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/unused"_ns, zStats.code.unused, "Memory allocated by one of the JITs to hold code, " "but which is currently unused.");
#define MAYBE_INLINE "The characters may be inline or on the malloc heap." #define MAYBE_OVERALLOCATED \ "Sometimes over-allocated to simplify string concatenation."
for (size_t i = 0; i < zStats.notableStrings.length(); i++) { const JS::NotableStringInfo& info = zStats.notableStrings[i];
MOZ_ASSERT(!zStats.isTotals);
// We don't do notable string detection when anonymizing, because // there's a good chance its for crash submission, and the memory // required for notable string detection is high.
MOZ_ASSERT(!anonymize);
// Viewing about:memory generates many notable strings which contain // "string(length=". If we report these as notable, then we'll create // even more notable strings the next time we open about:memory (unless // there's a GC in the meantime), and so on ad infinitum. // // To avoid cluttering up about:memory like this, we stick notable // strings which contain "string(length=" into their own bucket. #define STRING_LENGTH "string(length=" if (FindInReadable(nsLiteralCString(STRING_LENGTH), notableString)) {
stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1;
stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte;
stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1;
stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte; continue;
}
// Escape / to \ before we put notableString into the memory reporter // path, because we don't want any forward slashes in the string to // count as path separators.
nsCString escapedString(notableString);
escapedString.ReplaceSubstring("/", "\\");
if (stringsNotableAboutMemoryGCHeap > 0) {
MOZ_ASSERT(!zStats.isTotals);
REPORT_GC_BYTES(
pathPrefix + "strings/string(<about-memory>)/gc-heap"_ns,
stringsNotableAboutMemoryGCHeap, "Strings that contain the characters '" STRING_LENGTH "', which " "are probably from about:memory itself." MAYBE_INLINE " We filter them out rather than display them, because displaying " "them would create even more such strings every time about:memory " "is refreshed.");
}
if (stringsNotableAboutMemoryMallocHeap > 0) {
MOZ_ASSERT(!zStats.isTotals);
REPORT_BYTES(
pathPrefix + "strings/string(<about-memory>)/malloc-heap"_ns, KIND_HEAP,
stringsNotableAboutMemoryMallocHeap, "Non-inline string characters of strings that contain the " "characters '" STRING_LENGTH "', which are probably from " "about:memory itself. " MAYBE_OVERALLOCATED " We filter them out rather than display them, because displaying " "them would create even more such strings every time about:memory " "is refreshed.");
}
if (shapeInfo.shapesGCHeapDict > 0) {
REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/dict"_ns,
shapeInfo.shapesGCHeapDict, "Shapes in dictionary mode.");
}
if (shapeInfo.shapesGCHeapBase > 0) {
REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/base"_ns,
shapeInfo.shapesGCHeapBase, "Base shapes, which collate data common to many shapes.");
}
if (shapeInfo.shapesMallocHeapCache > 0) {
REPORT_BYTES(pathPrefix + "shapes/malloc-heap/shape-cache"_ns, KIND_HEAP,
shapeInfo.shapesMallocHeapCache, "Shape cache hash set for adding properties.");
}
if (sundriesGCHeap > 0) { // We deliberately don't use ZRREPORT_GC_BYTES here.
REPORT_GC_BYTES(
pathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, "The sum of all 'gc-heap' measurements that are too small to be " "worth showing individually.");
}
if (sundriesMallocHeap > 0) { // We deliberately don't use ZRREPORT_BYTES here.
REPORT_BYTES(
pathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, sundriesMallocHeap, "The sum of all 'malloc-heap' measurements that are too small to " "be worth showing individually.");
}
if (sundriesNonHeap > 0) { // We deliberately don't use ZRREPORT_NONHEAP_BYTES here.
REPORT_BYTES(pathPrefix + "sundries/other-heap"_ns, KIND_NONHEAP,
sundriesNonHeap, "The sum of non-malloc/gc measurements that are too small to " "be worth showing individually.");
}
if (gcTotalOut) {
*gcTotalOut += gcTotal;
}
#undef STRING_LENGTH
}
staticvoid ReportClassStats(const ClassInfo& classInfo, const nsACString& path,
nsIHandleReportCallback* handleReport,
nsISupports* data, size_t& gcTotal) { // We deliberately don't use ZRREPORT_BYTES, so that these per-class values // don't go into sundries.
if (classInfo.objectsGCHeap > 0) {
REPORT_GC_BYTES(path + "objects/gc-heap"_ns, classInfo.objectsGCHeap, "Objects, including fixed slots.");
}
if (classInfo.objectsMallocHeapElementsAsmJS > 0) {
REPORT_BYTES(path + "objects/malloc-heap/elements/asm.js"_ns, KIND_HEAP,
classInfo.objectsMallocHeapElementsAsmJS, "asm.js array buffer elements allocated in the malloc heap.");
}
if (classInfo.objectsMallocHeapGlobalData > 0) {
REPORT_BYTES(path + "objects/malloc-heap/global-data"_ns, KIND_HEAP,
classInfo.objectsMallocHeapGlobalData, "Data for global objects.");
}
if (classInfo.objectsNonHeapElementsShared > 0) {
REPORT_BYTES(
path + "objects/non-heap/elements/shared"_ns, KIND_NONHEAP,
classInfo.objectsNonHeapElementsShared, "Memory-mapped shared array buffer elements. These elements are " "shared between one or more runtimes; the reported size is divided " "by the buffer's refcount.");
}
// WebAssembly memories are always non-heap-allocated (mmap). We never put // these under sundries, because (a) in practice they're almost always // larger than the sundries threshold, and (b) we'd need a third category of // sundries ("non-heap"), which would be a pain. if (classInfo.objectsNonHeapElementsWasm > 0) {
REPORT_BYTES(path + "objects/non-heap/elements/wasm"_ns, KIND_NONHEAP,
classInfo.objectsNonHeapElementsWasm, "wasm/asm.js array buffer elements allocated outside both the " "malloc heap and the GC heap.");
} if (classInfo.objectsNonHeapElementsWasmShared > 0) {
REPORT_BYTES(
path + "objects/non-heap/elements/wasm-shared"_ns, KIND_NONHEAP,
classInfo.objectsNonHeapElementsWasmShared, "wasm/asm.js array buffer elements allocated outside both the " "malloc heap and the GC heap. These elements are shared between " "one or more runtimes; the reported size is divided by the " "buffer's refcount.");
}
// Note that we use realmDOMPathPrefix here. This is because we measure // orphan DOM nodes in the JS reporter, but we want to report them in a "dom" // sub-tree rather than a "js" sub-tree.
ZRREPORT_BYTES(
realmDOMPathPrefix + "orphan-nodes"_ns, realmStats.objectsPrivate, "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " "objects.");
ZRREPORT_GC_BYTES(
realmJSPathPrefix + "scripts/gc-heap"_ns, realmStats.scriptsGCHeap, "JSScript instances. There is one per user-defined function in a " "script, and one for the top-level code in a script.");
ZRREPORT_BYTES(realmJSPathPrefix + "scripts/malloc-heap/data"_ns,
realmStats.scriptsMallocHeapData, "Various variable-length tables in JSScripts.");
ZRREPORT_BYTES(realmJSPathPrefix + "baseline/data"_ns,
realmStats.baselineData, "The Baseline JIT's compilation data (BaselineScripts).");
ZRREPORT_BYTES(realmJSPathPrefix + "alloc-sites"_ns, realmStats.allocSites, "GC allocation site data associated with IC stubs.");
ZRREPORT_BYTES(realmJSPathPrefix + "ion-data"_ns, realmStats.ionData, "The IonMonkey JIT's compilation data (IonScripts).");
ZRREPORT_BYTES(realmJSPathPrefix + "jit-scripts"_ns, realmStats.jitScripts, "JIT data associated with scripts.");
ZRREPORT_BYTES(realmJSPathPrefix + "realm-object"_ns, realmStats.realmObject, "The JS::Realm object itself.");
ZRREPORT_BYTES(
realmJSPathPrefix + "realm-tables"_ns, realmStats.realmTables, "Realm-wide tables storing object group information and wasm instances.");
ZRREPORT_BYTES(realmJSPathPrefix + "inner-views"_ns,
realmStats.innerViewsTable, "The table for array buffer inner views.");
ZRREPORT_BYTES(
realmJSPathPrefix + "object-metadata"_ns, realmStats.objectMetadataTable, "The table used by debugging tools for tracking object metadata");
ZRREPORT_BYTES(realmJSPathPrefix + "saved-stacks-set"_ns,
realmStats.savedStacksSet, "The saved stacks set.");
ZRREPORT_BYTES(realmJSPathPrefix + "non-syntactic-lexical-scopes-table"_ns,
realmStats.nonSyntacticLexicalScopesTable, "The non-syntactic lexical scopes table.");
if (sundriesGCHeap > 0) { // We deliberately don't use ZRREPORT_GC_BYTES here.
REPORT_GC_BYTES(
realmJSPathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, "The sum of all 'gc-heap' measurements that are too small to be " "worth showing individually.");
}
if (sundriesMallocHeap > 0) { // We deliberately don't use ZRREPORT_BYTES here.
REPORT_BYTES(
realmJSPathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP,
sundriesMallocHeap, "The sum of all 'malloc-heap' measurements that are too small to " "be worth showing individually.");
}
// Report the rtStats.runtime numbers under "runtime/", and compute their // total for later.
size_t rtTotal = 0;
RREPORT_BYTES(rtPath + "runtime/runtime-object"_ns, KIND_HEAP,
rtStats.runtime.object, "The JSRuntime object.");
RREPORT_BYTES(rtPath + "runtime/atoms-table"_ns, KIND_HEAP,
rtStats.runtime.atomsTable, "The atoms table.");
RREPORT_BYTES(rtPath + "runtime/atoms-mark-bitmaps"_ns, KIND_HEAP,
rtStats.runtime.atomsMarkBitmaps, "Mark bitmaps for atoms held by each zone.");
RREPORT_BYTES(rtPath + "runtime/self-host-stencil"_ns, KIND_HEAP,
rtStats.runtime.selfHostStencil, "The self-hosting CompilationStencil.");
RREPORT_BYTES(rtPath + "runtime/contexts"_ns, KIND_HEAP,
rtStats.runtime.contexts, "JSContext objects and structures that belong to them.");
RREPORT_BYTES(
rtPath + "runtime/temporary"_ns, KIND_HEAP, rtStats.runtime.temporary, "Transient data (mostly parse nodes) held by the JSRuntime during " "compilation.");
for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) { const JS::NotableScriptSourceInfo& scriptSourceInfo =
rtStats.runtime.notableScriptSources[i];
// Escape / to \ before we put the filename into the memory reporter // path, because we don't want any forward slashes in the string to // count as path separators. Consumers of memory reporters (e.g. // about:memory) will convert them back to / after doing path // splitting.
nsCString escapedFilename; if (anonymize) {
escapedFilename.AppendPrintf("<anonymized-source-%d>", int(i));
} else {
nsDependentCString filename(scriptSourceInfo.filename_.get());
escapedFilename.Append(filename);
escapedFilename.ReplaceSubstring("/", "\\");
}
RREPORT_BYTES(rtPath + "runtime/gc/marker"_ns, KIND_HEAP,
rtStats.runtime.gc.marker, "The GC mark stack and gray roots.");
RREPORT_BYTES(rtPath + "runtime/gc/nursery-committed"_ns, KIND_NONHEAP,
rtStats.runtime.gc.nurseryCommitted, "Memory being used by the GC's nursery.");
RREPORT_BYTES(
rtPath + "runtime/gc/nursery-malloced-buffers"_ns, KIND_HEAP,
rtStats.runtime.gc.nurseryMallocedBuffers, "Out-of-line slots and elements belonging to objects in the nursery.");
RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/vals"_ns, KIND_HEAP,
rtStats.runtime.gc.storeBufferVals, "Values in the store buffer.");
RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/cells"_ns, KIND_HEAP,
rtStats.runtime.gc.storeBufferCells, "Cells in the store buffer.");
RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/slots"_ns, KIND_HEAP,
rtStats.runtime.gc.storeBufferSlots, "Slots in the store buffer.");
RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/whole-cells"_ns, KIND_HEAP,
rtStats.runtime.gc.storeBufferWholeCells, "Whole cells in the store buffer.");
RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/generics"_ns, KIND_HEAP,
rtStats.runtime.gc.storeBufferGenerics, "Generic things in the store buffer.");
// Report GC numbers that don't belong to a realm.
// We don't want to report decommitted memory in "explicit", so we just // change the leading "explicit/" to "decommitted/".
nsCString rtPath2(rtPath);
rtPath2.ReplaceLiteral(0, strlen("explicit"), "decommitted");
REPORT_GC_BYTES(
rtPath2 + "gc-heap/decommitted-pages"_ns, rtStats.gcHeapDecommittedPages, "GC arenas in non-empty chunks that is decommitted, i.e. it takes up " "address space but no physical memory or swap space.");
REPORT_GC_BYTES(
rtPath + "gc-heap/unused-chunks"_ns, rtStats.gcHeapUnusedChunks, "Empty GC chunks which will soon be released unless claimed for new " "allocations.");
REPORT_GC_BYTES(rtPath + "gc-heap/unused-arenas"_ns,
rtStats.gcHeapUnusedArenas, "Empty GC arenas within non-empty chunks.");
REPORT_GC_BYTES(rtPath + "gc-heap/chunk-admin"_ns, rtStats.gcHeapChunkAdmin, "Bookkeeping information within GC chunks.");
// gcTotal is the sum of everything we've reported for the GC heap. It // should equal rtStats.gcHeapChunkTotal.
MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal);
}
} // namespace xpc
class JSMainRuntimeRealmsReporter final : public nsIMemoryReporter {
~JSMainRuntimeRealmsReporter() = default;
public:
NS_DECL_ISUPPORTS
struct Data { int anonymizeID;
js::Vector<nsCString, 0, js::SystemAllocPolicy> paths;
};
NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport,
nsISupports* data, bool anonymize) override { // First we collect the realm paths. Then we report them. Doing // the two steps interleaved is a bad idea, because calling // |handleReport| from within RealmCallback() leads to all manner // of assertions.
// This is an orphan node. If we haven't already handled the sub-tree that // this node belongs to, measure the sub-tree's size and then record its // root so we don't measure it again.
nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot(); if (!orphanTree || mState.HaveSeenPtr(orphanTree.get())) { return 0;
}
// We combine the node size with nsStyleSizes here. It's not ideal, but it's // hard to get the style structs measurements out to nsWindowMemoryReporter. // Also, we drop mServoData in UnbindFromTree(), so in theory any // non-in-tree element won't have any style data to measure. // // FIXME(emilio): We should ideally not do this, since ShadowRoots keep // their StyleSheets alive even when detached from a document, and those // could be significant in theory. return sizes.getTotalSize();
}
~XPCJSRuntimeStats() { for (size_t i = 0; i != realmStatsVector.length(); ++i) { deletestatic_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra);
}
for (size_t i = 0; i != zoneStatsVector.length(); ++i) { deletestatic_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
}
}
// Get some global in this zone.
Rooted<Realm*> realm(dom::RootingCx(), js::GetAnyRealmInZone(zone)); if (realm) {
RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); if (global) {
RefPtr<nsGlobalWindowInner> window; if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { // The global is a |window| object. Use the path prefix that // we should have already created for it. if (mTopWindowPaths->Get(window->WindowID(), &extras->pathPrefix)) {
extras->pathPrefix.AppendLiteral("/js-");
}
}
}
}
// Get the realm's global. bool needZone = true;
RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); if (global) {
RefPtr<nsGlobalWindowInner> window; if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { // The global is a |window| object. Use the path prefix that // we should have already created for it. if (mWindowPaths->Get(window->WindowID(), &extras->jsPathPrefix)) {
extras->domPathPrefix.Assign(extras->jsPathPrefix);
extras->domPathPrefix.AppendLiteral("/dom/");
extras->jsPathPrefix.AppendLiteral("/js-");
needZone = false;
} else {
extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
extras->domPathPrefix.AssignLiteral( "explicit/dom/unknown-window-global?!/");
}
} else {
extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
extras->domPathPrefix.AssignLiteral( "explicit/dom/non-window-global?!/");
}
} else {
extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/");
extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/");
}
if (needZone) {
extras->jsPathPrefix +=
nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(realm));
}
// extras->jsPathPrefix is used for almost all the realm-specific // reports. At this point it has the form // "<something>realm(<rname>)/". // // extras->domPathPrefix is used for DOM orphan nodes, which are // counted by the JS reporter but reported as part of the DOM // measurements. At this point it has the form "<something>/dom/" if // this realm belongs to an nsGlobalWindow, and // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't // be used, because non-nsGlobalWindow realms shouldn't have // orphan DOM nodes).
// In the first step we get all the stats and stash them in a local // data structure. In the second step we pass all the stashed stats to // the callback. Separating these steps is important because the // callback may be a JS function, and executing JS while getting these // stats seems like a bad idea.
// Collect JS stats not associated with a Runtime such as helper threads or // global tracelogger data. We do this here in JSReporter::CollectReports // as this is used for the main Runtime in process.
JS::GlobalStats gStats(JSMallocSizeOf); if (!JS::CollectGlobalStats(&gStats)) { return;
}
// Report the sum of the runtime/ numbers.
REPORT_BYTES( "js-main-runtime/runtime"_ns, KIND_OTHER, rtTotal, "The sum of all measurements under 'explicit/js-non-window/runtime/'.");
// Report the number of HelperThread
REPORT("js-helper-threads/idle"_ns, KIND_OTHER, UNITS_COUNT,
gStats.helperThread.idleThreadCount, "The current number of idle JS HelperThreads.");
REPORT( "js-helper-threads/active"_ns, KIND_OTHER, UNITS_COUNT,
gStats.helperThread.activeThreadCount, "The current number of active JS HelperThreads. Memory held by these is" " not reported.");
// Report the numbers for memory used by wasm Runtime state.
REPORT_BYTES("wasm-runtime"_ns, KIND_OTHER, rtStats.runtime.wasmRuntime, "The memory used for wasm runtime bookkeeping.");
// Although wasm guard pages aren't committed in memory they can be very // large and contribute greatly to vsize and so are worth reporting. if (rtStats.runtime.wasmGuardPages > 0) {
REPORT_BYTES( "wasm-guard-pages"_ns, KIND_OTHER, rtStats.runtime.wasmGuardPages, "Guard pages mapped after the end of wasm memories, reserved for " "optimization tricks, but not committed and thus never contributing" " to RSS, only vsize.");
}
// Report the numbers for memory outside of realms.
REPORT_BYTES("js-main-runtime/gc-heap/unused-chunks"_ns, KIND_OTHER,
rtStats.gcHeapUnusedChunks, "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
REPORT_BYTES("js-main-runtime/gc-heap/unused-arenas"_ns, KIND_OTHER,
rtStats.gcHeapUnusedArenas, "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
REPORT_BYTES("js-main-runtime/gc-heap/chunk-admin"_ns, KIND_OTHER,
rtStats.gcHeapChunkAdmin, "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
// Report a breakdown of the committed GC space.
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/chunks"_ns, KIND_OTHER,
rtStats.gcHeapUnusedChunks, "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/arenas"_ns, KIND_OTHER,
rtStats.gcHeapUnusedArenas, "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/objects"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.object, "Unused object cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/strings"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.string, "Unused string cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/symbols"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol, "Unused symbol cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/shapes"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.shape, "Unused shape cells within non-empty arenas.");
REPORT_BYTES( "js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape, "Unused base shape cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/bigints"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.bigInt, "Unused BigInt cells within non-empty arenas.");
REPORT_BYTES( "js-main-runtime-gc-heap-committed/unused/gc-things/getter-setters"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.getterSetter, "Unused getter-setter cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/scopes"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.scope, "Unused scope cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/scripts"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.script, "Unused script cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, "Unused jitcode cells within non-empty arenas.");
REPORT_BYTES( "js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared, "Unused regexpshared cells within non-empty arenas.");
REPORT_BYTES( "js-main-runtime-gc-heap-committed/unused/gc-things/small-buffers"_ns,
KIND_OTHER, rtStats.zTotals.unusedGCThings.smallBuffer, "Unused small buffer cells within non-empty arenas.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/used/chunk-admin"_ns,
KIND_OTHER, rtStats.gcHeapChunkAdmin, "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
REPORT_BYTES("js-main-runtime-gc-heap-committed/used/arena-admin"_ns,
KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, "The same as 'js-main-runtime/zones/gc-heap-arena-admin'.");
// Report totals from per-zone GC buffer allocators.
MREPORT_BYTES("js-main-runtime-gc-buffers/used"_ns, KIND_OTHER,
rtStats.zTotals.gcBuffers.usedBytes, "Bookeeping information and padding within GC buffer memeory.");
MREPORT_BYTES("js-main-runtime-gc-buffers/free"_ns, KIND_OTHER,
rtStats.zTotals.gcBuffers.freeBytes, "Free space within GC buffer memeory.");
MREPORT_BYTES("js-main-runtime-gc-buffers/admin"_ns, KIND_OTHER,
rtStats.zTotals.gcBuffers.adminBytes, "Bookeeping information and padding within GC buffer memeory.");
REPORT("js-main-runtime-zone-count"_ns, KIND_OTHER, UNITS_COUNT,
rtStats.zoneStatsVector.length(), "Count of GC zones in the runtime.");
// Report xpconnect.
REPORT_BYTES("explicit/xpconnect/runtime"_ns, KIND_HEAP, xpcJSRuntimeSize, "The XPConnect runtime.");
REPORT_BYTES("explicit/xpconnect/wrappedjs"_ns, KIND_HEAP, wrappedJSSize, "Wrappers used to implement XPIDL interfaces with JS.");
REPORT_BYTES("explicit/js-non-window/helper-thread/heap-other"_ns, KIND_HEAP,
gStats.helperThread.stateData, "Memory used by HelperThreadState.");
REPORT_BYTES( "explicit/js-non-window/helper-thread/ion-compile-task"_ns, KIND_HEAP,
gStats.helperThread.ionCompileTask, "The memory used by IonCompileTasks waiting in HelperThreadState.");
REPORT_BYTES( "explicit/js-non-window/helper-thread/wasm-compile"_ns, KIND_HEAP,
gStats.helperThread.wasmCompile, "The memory used by Wasm compilations waiting in HelperThreadState.");
REPORT_BYTES("explicit/js-non-window/helper-thread/contexts"_ns, KIND_HEAP,
gStats.helperThread.contexts, "The memory used by the JSContexts in HelperThreadState.");
}
switch (id) { // Disable clone.deserialize metrics on Android for perf (bug 1898515). #ifndef MOZ_WIDGET_ANDROID case JSMetric::DESERIALIZE_BYTES:
glean::performance_clone_deserialize::size.Accumulate(sample); break; case JSMetric::DESERIALIZE_ITEMS:
glean::performance_clone_deserialize::items.AccumulateSingleSample(
sample); break; case JSMetric::DESERIALIZE_US:
glean::performance_clone_deserialize::time.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; #endif// MOZ_WIDGET_ANDROID case JSMetric::GC_MS:
glean::javascript_gc::total_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_MINOR_US:
glean::javascript_gc::minor_time.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; case JSMetric::GC_PREPARE_MS:
glean::javascript_gc::prepare_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_MARK_ROOTS_US:
glean::javascript_gc::mark_roots_time.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; case JSMetric::GC_MARK_MS:
glean::javascript_gc::mark_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_SWEEP_MS:
glean::javascript_gc::sweep_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_COMPACT_MS:
glean::javascript_gc::compact_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_SLICE_MS:
glean::javascript_gc::slice_time.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::ION_COMPILE_TIME:
glean::javascript_ion::compile_time.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; case JSMetric::GC_BUDGET_MS_2:
glean::javascript_gc::budget.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_BUDGET_OVERRUN:
glean::javascript_gc::budget_overrun.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; case JSMetric::GC_ANIMATION_MS:
glean::javascript_gc::animation.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_MAX_PAUSE_MS_2:
glean::javascript_gc::max_pause.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_MARK_GRAY_MS_2:
glean::javascript_gc::mark_gray.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_MARK_WEAK_MS:
glean::javascript_gc::mark_weak.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_TIME_BETWEEN_S:
glean::javascript_gc::time_between.AccumulateRawDuration(
TimeDuration::FromSeconds(sample)); break; case JSMetric::GC_TIME_BETWEEN_SLICES_MS:
glean::javascript_gc::time_between_slices.AccumulateRawDuration(
TimeDuration::FromMilliseconds(sample)); break; case JSMetric::GC_TASK_START_DELAY_US:
glean::javascript_gc::task_start_delay.AccumulateRawDuration(
TimeDuration::FromMicroseconds(sample)); break; case JSMetric::GC_MMU_50:
glean::javascript_gc::mmu_50.AccumulateSingleSample(sample); break; case JSMetric::GC_NURSERY_PROMOTION_RATE:
glean::javascript_gc::nursery_promotion_rate.AccumulateSingleSample(
sample); break; case JSMetric::GC_TENURED_SURVIVAL_RATE:
glean::javascript_gc::tenured_survival_rate.AccumulateSingleSample(
sample); break; case JSMetric::GC_PARALLEL_MARK_UTILIZATION:
glean::javascript_gc::parallel_mark_utilization.AccumulateSingleSample(
sample); break; case JSMetric::GC_NURSERY_BYTES_2:
glean::javascript_gc::nursery_bytes.Accumulate(sample); break; case JSMetric::GC_EFFECTIVENESS:
glean::javascript_gc::effectiveness.AccumulateSingleSample(sample); break; case JSMetric::GC_ZONE_COUNT:
glean::javascript_gc::zone_count.AccumulateSingleSample(sample); break; case JSMetric::GC_ZONES_COLLECTED:
glean::javascript_gc::zones_collected.AccumulateSingleSample(sample); break; case JSMetric::GC_PRETENURE_COUNT_2:
glean::javascript_gc::pretenure_count.AccumulateSingleSample(sample); break; case JSMetric::GC_MARK_RATE_2:
glean::javascript_gc::mark_rate.AccumulateSingleSample(sample); break; case JSMetric::GC_SLICE_COUNT:
glean::javascript_gc::slice_count.AccumulateSingleSample(sample); break; case JSMetric::GC_PARALLEL_MARK_SPEEDUP:
glean::javascript_gc::parallel_mark_speedup.AccumulateSingleSample(
sample); break; case JSMetric::GC_PARALLEL_MARK_INTERRUPTIONS:
glean::javascript_gc::parallel_mark_interruptions.AccumulateSingleSample(
sample); break; case JSMetric::GC_IS_COMPARTMENTAL: if (sample) {
glean::javascript_gc::is_zone_gc
.EnumGet(glean::javascript_gc::IsZoneGcLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::is_zone_gc
.EnumGet(glean::javascript_gc::IsZoneGcLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_BUDGET_WAS_INCREASED: if (sample) {
glean::javascript_gc::budget_was_increased
.EnumGet(glean::javascript_gc::BudgetWasIncreasedLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::budget_was_increased
.EnumGet(glean::javascript_gc::BudgetWasIncreasedLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_SLICE_WAS_LONG: if (sample) {
glean::javascript_gc::slice_was_long
.EnumGet(glean::javascript_gc::SliceWasLongLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::slice_was_long
.EnumGet(glean::javascript_gc::SliceWasLongLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_RESET: if (sample) {
glean::javascript_gc::reset
.EnumGet(glean::javascript_gc::ResetLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::reset
.EnumGet(glean::javascript_gc::ResetLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_NON_INCREMENTAL: if (sample) {
glean::javascript_gc::non_incremental
.EnumGet(glean::javascript_gc::NonIncrementalLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::non_incremental
.EnumGet(glean::javascript_gc::NonIncrementalLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_PARALLEL_MARK: if (sample) {
glean::javascript_gc::parallel_mark_used
.EnumGet(glean::javascript_gc::ParallelMarkUsedLabel::eTrue)
.Add(1);
} else {
glean::javascript_gc::parallel_mark_used
.EnumGet(glean::javascript_gc::ParallelMarkUsedLabel::eFalse)
.Add(1);
} break; case JSMetric::GC_REASON_2: { // Assert that every reason has an associated glean label.
static_assert(static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) == static_cast<uint8_t>(
glean::javascript_gc::ReasonLabel::e__Other__), "GC reason enum and glean::javascript_gc::reason labels do " "not match.");
MOZ_ASSERT(static_cast<JS::GCReason>(sample) <=
JS::GCReason::LAST_FIREFOX_REASON, "Invalid GC Reason.");
nsAutoCString reason(
JS::ExplainGCReason(static_cast<JS::GCReason>(sample)));
glean::javascript_gc::reason.Get(reason).Add(1);
} break; case JSMetric::GC_RESET_REASON: {
MOZ_ASSERT(
sample < static_cast<uint32_t>(
glean::javascript_gc::ResetReasonLabel::e__Other__), "Reason does not exist in the reset_reason labels list.");
nsAutoCString reason(JS::ExplainGCAbortReason(sample));
glean::javascript_gc::reset_reason.Get(reason).Add(1);
} break; case JSMetric::GC_NON_INCREMENTAL_REASON: {
MOZ_ASSERT(
sample < static_cast<uint32_t>(
glean::javascript_gc::NonIncrementalReasonLabel::e__Other__), "Reason does not exist in the non_incremental_reason labels list.");
nsAutoCString reason(JS::ExplainGCAbortReason(sample));
glean::javascript_gc::non_incremental_reason.Get(reason).Add(1);
} break; case JSMetric::GC_MINOR_REASON: { // Assert that every reason has an associated glean label.
static_assert( static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) == static_cast<uint8_t>(
glean::javascript_gc::MinorReasonLabel::e__Other__), "GC reason enum and glean::javascript_gc::reason labels do not " "match.");
MOZ_ASSERT(static_cast<JS::GCReason>(sample) <=
JS::GCReason::LAST_FIREFOX_REASON, "Invalid GC Reason.");
nsAutoCString reason(
JS::ExplainGCReason(static_cast<JS::GCReason>(sample)));
glean::javascript_gc::minor_reason.Get(reason).Add(1);
} break; case JSMetric::GC_MINOR_REASON_LONG: { // Assert that every reason has an associated glean label.
static_assert( static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) == static_cast<uint8_t>(
glean::javascript_gc::MinorReasonLongLabel::e__Other__), "GC reason enum and glean::javascript_gc::reason labels do not " "match.");
MOZ_ASSERT(static_cast<JS::GCReason>(sample) <=
JS::GCReason::LAST_FIREFOX_REASON, "Invalid GC Reason.");
default: // The rest aren't relayed to Glean. break;
}
}
staticvoid SetUseCounterCallback(JSObject* obj, JSUseCounter counter) { switch (counter) { case JSUseCounter::ASMJS:
SetUseCounter(obj, eUseCounter_custom_JS_asmjs); return; case JSUseCounter::WASM:
SetUseCounter(obj, eUseCounter_custom_JS_wasm); return; case JSUseCounter::WASM_LEGACY_EXCEPTIONS:
SetUseCounter(obj, eUseCounter_custom_JS_wasm_legacy_exceptions); return; case JSUseCounter::ISHTMLDDA_FUSE:
SetUseCounter(obj, eUseCounter_custom_JS_isHTMLDDA_fuse); return; case JSUseCounter::OPTIMIZE_GET_ITERATOR_FUSE:
SetUseCounter(obj, eUseCounter_custom_JS_optimizeGetIterator_fuse); return; case JSUseCounter::THENABLE_USE:
SetUseCounter(obj, eUseCounter_custom_JS_thenable); return; case JSUseCounter::THENABLE_USE_PROTO:
SetUseCounter(obj, eUseCounter_custom_JS_thenable_proto); return; case JSUseCounter::THENABLE_USE_STANDARD_PROTO:
SetUseCounter(obj, eUseCounter_custom_JS_thenable_standard_proto); return; case JSUseCounter::LEGACY_LANG_SUBTAG:
SetUseCounter(obj, eUseCounter_custom_JS_legacy_lang_subtag); return; case JSUseCounter::IC_STUB_TOO_LARGE:
SetUseCounter(obj, eUseCounter_custom_JS_ic_stub_too_large); return; case JSUseCounter::IC_STUB_OOM:
SetUseCounter(obj, eUseCounter_custom_JS_ic_stub_oom); return; case JSUseCounter::ERRORSTACK_GETTER:
SetUseCounter(obj, eUseCounter_custom_JS_errorstack_getter); return; case JSUseCounter::ERRORSTACK_GETTER_NO_ERRORDATA:
SetUseCounter(obj, eUseCounter_custom_JS_errorstack_getter_no_errordata); return; case JSUseCounter::ERRORSTACK_SETTER:
SetUseCounter(obj, eUseCounter_custom_JS_errorstack_setter); return; case JSUseCounter::ERRORSTACK_SETTER_NONSTRING:
SetUseCounter(obj, eUseCounter_custom_JS_errorstack_setter_nonstring); return; case JSUseCounter::ERRORSTACK_SETTER_NO_ERRORDATA:
SetUseCounter(obj, eUseCounter_custom_JS_errorstack_setter_no_errordata); return; case JSUseCounter::DATEPARSE:
SetUseCounter(obj, eUseCounter_custom_JS_dateparse); return; case JSUseCounter::DATEPARSE_IMPL_DEF:
SetUseCounter(obj, eUseCounter_custom_JS_dateparse_impl_def); return; case JSUseCounter::COUNT: break;
}
MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id");
}
staticvoid GetRealmNameCallback(JSContext* cx, Realm* realm, char* buf,
size_t bufsize, const JS::AutoRequireNoGC& nogc) {
nsCString name; // This is called via the JSAPI and isn't involved in memory reporting, so // we don't need to anonymize realm names. int anonymizeID = 0;
GetRealmName(realm, name, &anonymizeID, /* replaceSlashes = */ false); if (name.Length() >= bufsize) {
name.Truncate(bufsize - 1);
}
memcpy(buf, name.get(), name.Length() + 1);
}
staticvoid DestroyRealm(JS::GCContext* gcx, JS::Realm* realm) { // Get the current compartment private into an AutoPtr (which will do the // cleanup for us), and null out the private field.
mozilla::UniquePtr<RealmPrivate> priv(RealmPrivate::Get(realm));
JS::SetRealmPrivate(realm, nullptr);
}
if (!mozilla::dom::TryPreserveWrapper(obj)) { returnfalse;
}
MOZ_ASSERT(!mozilla::dom::HasReleasedWrapper(obj), "There should be no released wrapper since we just preserved it");
returntrue;
}
static nsresult ReadSourceFromFilename(JSContext* cx, constchar* filename,
char16_t** twoByteSource, char** utf8Source, size_t* len) {
MOZ_ASSERT(*len == 0);
MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), "must be called requesting only one of UTF-8 or UTF-16 source");
MOZ_ASSERT_IF(twoByteSource, !*twoByteSource);
MOZ_ASSERT_IF(utf8Source, !*utf8Source);
nsresult rv;
// mozJSSubScriptLoader prefixes the filenames of the scripts it loads with // the filename of its caller. Axe that if present. constchar* arrow; while ((arrow = strstr(filename, " -> "))) {
filename = arrow + strlen(" -> ");
}
// Get the URI.
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), filename);
NS_ENSURE_SUCCESS(rv, rv);
// Technically, this should be SIZE_MAX, but we don't run on machines // where that would be less than UINT32_MAX, and the latter is already // well beyond a reasonable limit. if (rawLen > UINT32_MAX) { return NS_ERROR_FILE_TOO_BIG;
}
// Allocate a buffer the size of the file to initially fill with the UTF-8 // contents of the file. Use the JS allocator so that if UTF-8 source was // requested, we can return this memory directly.
JS::UniqueChars buf(js_pod_malloc<char>(rawLen)); if (!buf) { return NS_ERROR_OUT_OF_MEMORY;
}
char* ptr = buf.get(); char* end = ptr + rawLen; while (ptr < end) {
uint32_t bytesRead;
rv = scriptStream->Read(ptr, PointerRangeSize(ptr, end), &bytesRead); if (NS_FAILED(rv)) { return rv;
}
MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF");
ptr += bytesRead;
}
if (utf8Source) { // |buf| is already UTF-8, so we can directly return it.
*len = rawLen;
*utf8Source = buf.release();
} else {
MOZ_ASSERT(twoByteSource != nullptr);
// |buf| can't be directly returned -- convert it to UTF-16.
// On success this overwrites |chars| and |*len|.
JS::UniqueTwoByteChars chars;
rv = ScriptLoader::ConvertToUTF16(
scriptChannel, reinterpret_cast<constunsignedchar*>(buf.get()),
rawLen, u"UTF-8"_ns, nullptr, chars, *len);
NS_ENSURE_SUCCESS(rv, rv);
if (!chars) { return NS_ERROR_FAILURE;
}
*twoByteSource = chars.release();
}
return NS_OK;
}
// The JS engine calls this object's 'load' member function when it needs // the source for a chrome JS function. See the comment in the XPCJSRuntime // constructor. class XPCJSSourceHook : public js::SourceHook { bool load(JSContext* cx, constchar* filename, char16_t** twoByteSource, char** utf8Source, size_t* length) override {
MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), "must be called requesting only one of UTF-8 or UTF-16 source");
// Subclass of JS::ubi::Base for DOM reflector objects for the JS::ubi::Node // memory analysis framework; see js/public/UbiNode.h. In // XPCJSRuntime::Initialize, we register the ConstructUbiNode function as a hook // with the SpiderMonkey runtime for it to use to construct ubi::Nodes of this // class for JSObjects whose class has the JSCLASS_IS_DOMJSCLASS flag set. // ReflectorNode specializes Concrete<JSObject> for DOM reflector nodes, // reporting the edge from the JSObject to the nsINode it represents, in // addition to the usual edges departing any normal JSObject. namespace JS { namespace ubi { class ReflectorNode : public Concrete<JSObject> { protected: explicit ReflectorNode(JSObject* ptr) : Concrete<JSObject>(ptr) {}
js::UniquePtr<EdgeRange> ReflectorNode::edges(JSContext* cx, bool wantNames) const {
js::UniquePtr<SimpleEdgeRange> range(static_cast<SimpleEdgeRange*>(
Concrete<JSObject>::edges(cx, wantNames).release())); if (!range) { return nullptr;
} // UNWRAP_NON_WRAPPER_OBJECT assumes the object is completely initialized, // but ours may not be. Luckily, UnwrapDOMObjectToISupports checks for the // uninitialized case (and returns null if uninitialized), so we can use that // to guard against uninitialized objects.
nsISupports* supp = UnwrapDOMObjectToISupports(&get()); if (supp) {
JS::AutoSuppressGCAnalysis nogc; // bug 1582326
nsINode* node; // UnwrapDOMObjectToISupports can only return non-null if its argument is // an actual DOM object, not a cross-compartment wrapper. if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Node, &get(), node))) {
char16_t* edgeName = nullptr; if (wantNames) {
edgeName = NS_xstrdup(u"Reflected Node");
} if (!range->addEdge(Edge(edgeName, node))) { return nullptr;
}
}
} return js::UniquePtr<EdgeRange>(range.release());
}
void XPCJSRuntime::Initialize(JSContext* cx) { // these jsids filled in later when we have a JSContext to work with.
mStrIDs[0] = JS::PropertyKey::Void();
// Unconstrain the runtime's threshold on nominal heap size, to avoid // triggering GC too often if operating continuously near an arbitrary // finite threshold (0xffffffff is infinity for uint32_t parameters). // This leaves the maximum-JS_malloc-bytes threshold still in effect // to cause period, and we hope hygienic, last-ditch GCs from within // the GC's allocator.
JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
// The WasmAltDataType is build by the JS engine from the build id.
JS::SetProcessBuildIdOp(GetBuildId);
FetchUtil::InitWasmAltDataType();
// The JS engine needs to keep the source code around in order to implement // Function.prototype.toSource(). It'd be nice to not have to do this for // chrome code and simply stub out requests for source on it. Life is not so // easy, unfortunately. Nobody relies on chrome toSource() working in core // browser code, but chrome tests use it. The worst offenders are addons, // which like to monkeypatch chrome functions by calling toSource() on them // and using regular expressions to modify them. We avoid keeping most browser // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when // compiling some chrome code. This causes the JS engine not save the source // code in memory. When the JS engine is asked to provide the source for a // function compiled with LAZY_SOURCE, it calls SourceHook to load it. /// // Note we do have to retain the source code in memory for scripts compiled in // isRunOnce mode and compiled function bodies (from // JS::CompileFunction). In practice, this means content scripts and event // handlers.
mozilla::UniquePtr<XPCJSSourceHook> hook(new XPCJSSourceHook);
js::SetSourceHook(cx, std::move(hook));
// Set the callback for reporting memory to ubi::Node.
JS::ubi::SetConstructUbiNodeForDOMObjectCallback(cx, &ConstructUbiNode);
xpc_LocalizeRuntime(JS_GetRuntime(cx));
}
bool XPCJSRuntime::InitializeStrings(JSContext* cx) { // if it is our first context then we need to generate our string ids if (mStrIDs[0].isVoid()) {
RootedString str(cx); for (unsigned i = 0; i < XPCJSContext::IDX_TOTAL_COUNT; i++) {
str = JS_AtomizeAndPinString(cx, mStrings[i]); if (!str) {
mStrIDs[0] = JS::PropertyKey::Void(); returnfalse;
}
mStrIDs[i] = PropertyKey::fromPinnedString(str);
}
if (!mozilla::dom::DefineStaticJSVals(cx)) { returnfalse;
}
}
XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); // Nothing here can GC. The analysis would otherwise think that ~nsCOMPtr // could GC, but that's only possible if nsIXPCScriptable::GetJSClass() // somehow released a reference to the nsIXPCScriptable, which isn't going to // happen.
JS::AutoSuppressGCAnalysis nogc;
nsCOMPtr<nsIXPCScriptable> scr = p->GetScriptable(); if (!scr) { returnfalse;
}
// A tearoff holds a strong reference to its native object // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative // will be held alive through tearoff's XPC_WN_TEAROFF_FLAT_OBJECT_SLOT, // which points to the XPCWrappedNative's mFlatJSObject.
XPCWrappedNativeTearOff* to = XPCWrappedNativeTearOff::Get(obj);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "XPCWrappedNativeTearOff::Get(obj)->mNative");
cb.NoteXPCOMChild(to->GetNative()); returntrue;
}
void XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) {
MOZ_ASSERT(cb, "null callback"); bool found = extraGCCallbacks.RemoveElement(cb); if (!found) {
NS_ERROR("Removing a callback which was never added.");
}
}
JSObject* XPCJSRuntime::GetUAWidgetScope(JSContext* cx,
nsIPrincipal* principal) {
MOZ_ASSERT(!principal->IsSystemPrincipal(), "Running UA Widget in chrome");
RootedObject scope(cx); do {
RefPtr<BasePrincipal> key = BasePrincipal::Cast(principal); if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) {
scope = p->value(); break; // Need ~RefPtr to run, and potentially GC, before returning.
}
// Use an ExpandedPrincipal to create asymmetric security.
MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal));
nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray{principal};
RefPtr<ExpandedPrincipal> ep = ExpandedPrincipal::Create(
principalAsArray, principal->OriginAttributesRef());
mUnprivilegedJunkScope =
SandboxPrivate::GetPrivate(sandbox.toObjectOrNull());
}
MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(), "Wrapper should have same lifetime as weak reference"); return mUnprivilegedJunkScope->GetWrapper();
}
void XPCJSRuntime::DeleteSingletonScopes() { // We're pretty late in shutdown, so we call ReleaseWrapper on the scopes. // This way the GC can collect them immediately, and we don't rely on the CC // to clean up. if (RefPtr<SandboxPrivate> sandbox = mUnprivilegedJunkScope.get()) {
sandbox->ReleaseWrapper(sandbox);
mUnprivilegedJunkScope = nullptr;
}
}
uint32_t GetAndClampCPUCount() { // See HelperThreads.cpp for why we want between 2-8 threads
int32_t proc = GetNumberOfProcessors(); if (proc < 2) { return 2;
} return std::min(proc, 8);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.84 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.