summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/XPCJSRuntime.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/xpconnect/src/XPCJSRuntime.cpp3217
1 files changed, 3217 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp
new file mode 100644
index 0000000000..c4d272b950
--- /dev/null
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -0,0 +1,3217 @@
+/* -*- 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/. */
+
+/* Per JSRuntime object */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+#include "xpcprivate.h"
+#include "xpcpublic.h"
+#include "XPCMaps.h"
+#include "XPCWrapper.h"
+#include "XPCJSMemoryReporter.h"
+#include "XrayWrapper.h"
+#include "WrapperFactory.h"
+#include "mozJSModuleLoader.h"
+#include "nsNetUtil.h"
+#include "nsContentSecurityUtils.h"
+
+#include "nsExceptionHandler.h"
+#include "nsIMemoryInfoDumper.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/Document.h"
+#include "nsIRunnable.h"
+#include "nsIPlatformInfo.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
+#include "nsScriptSecurityManager.h"
+#include "nsThreadPool.h"
+#include "nsWindowSizes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "nsContentUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsCycleCollector.h"
+#include "jsapi.h"
+#include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
+#include "js/experimental/SourceHook.h" // js::{,Set}SourceHook
+#include "js/GCAPI.h"
+#include "js/MemoryFunctions.h"
+#include "js/MemoryMetrics.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/RealmIterators.h"
+#include "js/SliceBudget.h"
+#include "js/UbiNode.h"
+#include "js/UbiNodeUtils.h"
+#include "js/friend/UsageStatistics.h" // JSMetric, JS_SetAccumulateTelemetryCallback
+#include "js/friend/WindowProxy.h" // js::SetWindowProxyClass
+#include "js/friend/XrayJitInfo.h" // JS::SetXrayJitInfo
+#include "js/Utility.h" // JS::UniqueTwoByteChars
+#include "mozilla/dom/AbortSignalBinding.h"
+#include "mozilla/dom/GeneratedAtomList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "AccessCheck.h"
+#include "nsGlobalWindowInner.h"
+#include "nsAboutProtocolUtils.h"
+
+#include "NodeUbiReporting.h"
+#include "ExpandedPrincipal.h"
+#include "nsIInputStream.h"
+#include "nsJSPrincipals.h"
+#include "nsJSEnvironment.h"
+#include "XPCInlines.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace xpc;
+using namespace JS;
+using namespace js;
+using mozilla::dom::PerThreadAtomCache;
+
+/***************************************************************************/
+
+const char* const XPCJSRuntime::mStrings[] = {
+ "constructor", // IDX_CONSTRUCTOR
+ "toString", // IDX_TO_STRING
+ "toSource", // IDX_TO_SOURCE
+ "value", // IDX_VALUE
+ "QueryInterface", // IDX_QUERY_INTERFACE
+ "Components", // IDX_COMPONENTS
+ "Cc", // IDX_CC
+ "Ci", // IDX_CI
+ "Cr", // IDX_CR
+ "Cu", // IDX_CU
+ "Services", // IDX_SERVICES
+ "wrappedJSObject", // IDX_WRAPPED_JSOBJECT
+ "prototype", // IDX_PROTOTYPE
+ "eval", // IDX_EVAL
+ "controllers", // IDX_CONTROLLERS
+ "Controllers", // IDX_CONTROLLERS_CLASS
+ "length", // IDX_LENGTH
+ "name", // IDX_NAME
+ "undefined", // IDX_UNDEFINED
+ "", // IDX_EMPTYSTRING
+ "fileName", // IDX_FILENAME
+ "lineNumber", // IDX_LINENUMBER
+ "columnNumber", // IDX_COLUMNNUMBER
+ "stack", // IDX_STACK
+ "message", // IDX_MESSAGE
+ "cause", // IDX_CAUSE
+ "errors", // IDX_ERRORS
+ "lastIndex", // IDX_LASTINDEX
+ "then", // IDX_THEN
+ "isInstance", // IDX_ISINSTANCE
+ "Infinity", // IDX_INFINITY
+ "NaN", // IDX_NAN
+ "classId", // IDX_CLASS_ID
+ "interfaceId", // IDX_INTERFACE_ID
+ "initializer", // IDX_INITIALIZER
+ "print", // IDX_PRINT
+ "fetch", // IDX_FETCH
+ "crypto", // IDX_CRYPTO
+ "indexedDB", // IDX_INDEXEDDB
+ "structuredClone", // IDX_STRUCTUREDCLONE
+};
+
+/***************************************************************************/
+
+// *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);
+
+ TimeStamp start = TimeStamp::Now();
+ // 2 ms budget, given that kICCSliceBudget is only 3 ms
+ js::SliceBudget budget = js::SliceBudget(js::TimeBudget(2));
+ bool hadSnowWhiteObjects =
+ nsCycleCollector_doDeferredDeletionWithBudget(budget);
+ Telemetry::Accumulate(
+ Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
+ uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
+ if (hadSnowWhiteObjects && !mContinuation) {
+ mContinuation = true;
+ if (NS_FAILED(Dispatch())) {
+ mActive = false;
+ }
+ } else {
+ mActive = false;
+ }
+ return NS_OK;
+ }
+
+ nsresult Dispatch() {
+ nsCOMPtr<nsIRunnable> self(this);
+ return NS_DispatchToCurrentThreadQueue(self.forget(), 1000,
+ EventQueuePriority::Idle);
+ }
+
+ void Start(bool aContinuation = false, bool aPurge = false) {
+ if (mContinuation) {
+ mContinuation = aContinuation;
+ }
+ mPurge = aPurge;
+ if (!mActive && NS_SUCCEEDED(Dispatch())) {
+ mActive = true;
+ }
+ }
+
+ AsyncFreeSnowWhite()
+ : Runnable("AsyncFreeSnowWhite"),
+ mContinuation(false),
+ mActive(false),
+ mPurge(false) {}
+
+ public:
+ bool mContinuation;
+ bool mActive;
+ bool mPurge;
+};
+
+namespace xpc {
+
+CompartmentPrivate::CompartmentPrivate(
+ JS::Compartment* c, mozilla::UniquePtr<XPCWrappedNativeScope> scope,
+ mozilla::BasePrincipal* origin, const SiteIdentifier& site)
+ : originInfo(origin, site),
+ wantXrays(false),
+ allowWaivers(true),
+ isWebExtensionContentScript(false),
+ isUAWidgetCompartment(false),
+ hasExclusiveExpandos(false),
+ wasShutdown(false),
+ mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()),
+ mScope(std::move(scope)) {
+ MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
+}
+
+CompartmentPrivate::~CompartmentPrivate() {
+ MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
+}
+
+void CompartmentPrivate::SystemIsBeingShutDown() {
+ // We may call this multiple times when the compartment contains more than one
+ // realm.
+ if (!wasShutdown) {
+ mWrappedJSMap->ShutdownMarker();
+ wasShutdown = true;
+ }
+}
+
+RealmPrivate::RealmPrivate(JS::Realm* realm) : scriptability(realm) {
+ mozilla::PodArrayZero(wrapperDenialWarnings);
+}
+
+/* static */
+void RealmPrivate::Init(HandleObject aGlobal, const SiteIdentifier& aSite) {
+ MOZ_ASSERT(aGlobal);
+ DebugOnly<const JSClass*> clasp = JS::GetClass(aGlobal);
+ MOZ_ASSERT(clasp->slot0IsISupports() || dom::IsDOMClass(clasp));
+
+ Realm* realm = GetObjectRealmOrNull(aGlobal);
+
+ // 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();
+}
+
+static bool 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)) {
+ return false;
+ }
+
+ // -- 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)) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) {
+ return false;
+ }
+
+ nsAutoCString scheme;
+ if (NS_FAILED(uri->GetScheme(scheme))) {
+ return false;
+ }
+
+ // 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")) {
+ return false;
+ }
+
+ uri.forget(aURI);
+ return true;
+}
+
+bool RealmPrivate::TryParseLocationURI(RealmPrivate::LocationHint aLocationHint,
+ nsIURI** aURI) {
+ if (!aURI) {
+ return false;
+ }
+
+ // Need to parse the URI.
+ if (location.IsEmpty()) {
+ return false;
+ }
+
+ // 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.
+
+ static const nsDependentCString from("(from: ");
+ static const nsDependentCString arrow(" -> ");
+ static const size_t fromLength = from.Length();
+ static const size_t arrowLength = arrow.Length();
+
+ // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName
+ int32_t idx = location.Find(from);
+ if (idx < 0) {
+ return TryParseLocationURICandidate(location, aLocationHint, aURI);
+ }
+
+ // 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)) {
+ return true;
+ }
+
+ // 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)) {
+ return true;
+ }
+
+ // 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");
+}
+
+static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) {
+ // System principal gets a free pass.
+ if (aPrincipal->IsSystemPrincipal()) {
+ return true;
+ }
+
+ auto* principal = BasePrincipal::Cast(aPrincipal);
+
+ // ExpandedPrincipal gets a free pass.
+ if (principal->Is<ExpandedPrincipal>()) {
+ return true;
+ }
+
+ // WebExtension principals get a free pass.
+ if (principal->AddonPolicy()) {
+ return true;
+ }
+
+ // pdf.js is a special-case too.
+ if (nsContentUtils::IsPDFJS(principal)) {
+ return true;
+ }
+
+ // 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)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RealmPrivate::RegisterStackFrame(JSStackFrameBase* aFrame) {
+ mJSStackFrames.PutEntry(aFrame);
+}
+
+void RealmPrivate::UnregisterStackFrame(JSStackFrameBase* aFrame) {
+ mJSStackFrames.RemoveEntry(aFrame);
+}
+
+void RealmPrivate::NukeJSStackFrames() {
+ for (const auto& key : mJSStackFrames.Keys()) {
+ key->Clear();
+ }
+
+ mJSStackFrames.Clear();
+}
+
+void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
+ RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
+ if (!realmPrivate) {
+ return;
+ }
+
+ realmPrivate->RegisterStackFrame(aStackFrame);
+}
+
+void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) {
+ RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
+ if (!realmPrivate) {
+ return;
+ }
+
+ realmPrivate->UnregisterStackFrame(aStackFrame);
+}
+
+void NukeJSStackFrames(JS::Realm* aRealm) {
+ RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm);
+ if (!realmPrivate) {
+ return;
+ }
+
+ realmPrivate->NukeJSStackFrames();
+}
+
+Scriptability::Scriptability(JS::Realm* realm)
+ : mScriptBlocks(0),
+ mWindowAllowsScript(true),
+ mScriptBlockedByPolicy(false) {
+ nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
+
+ 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 Scriptability::Allowed() {
+ return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0;
+}
+
+bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; }
+
+void Scriptability::Block() { ++mScriptBlocks; }
+
+void Scriptability::Unblock() {
+ MOZ_ASSERT(mScriptBlocks > 0);
+ --mScriptBlocks;
+}
+
+void Scriptability::SetWindowAllowsScript(bool aAllowed) {
+ mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy;
+}
+
+/* static */
+bool Scriptability::AllowedIfExists(JSObject* aScope) {
+ RealmPrivate* realmPrivate = RealmPrivate::Get(aScope);
+ return realmPrivate ? realmPrivate->scriptability.Allowed() : true;
+}
+
+/* static */
+Scriptability& Scriptability::Get(JSObject* aScope) {
+ return RealmPrivate::Get(aScope)->scriptability;
+}
+
+bool IsUAWidgetCompartment(JS::Compartment* compartment) {
+ // We always eagerly create compartment privates for UA Widget compartments.
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ return priv && priv->isUAWidgetCompartment;
+}
+
+bool IsUAWidgetScope(JS::Realm* realm) {
+ return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm));
+}
+
+bool IsInUAWidgetScope(JSObject* obj) {
+ return IsUAWidgetCompartment(JS::GetCompartment(obj));
+}
+
+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);
+}
+
+bool MightBeWebContentCompartment(JS::Compartment* compartment) {
+ if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) {
+ return priv->originInfo.MightBeWebContent();
+ }
+
+ // No CompartmentPrivate; try IsSystemCompartment.
+ return !js::IsSystemCompartment(compartment);
+}
+
+bool CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const {
+ return mOrigin->FastEquals(aOther);
+}
+
+/* static */
+bool CompartmentOriginInfo::Subsumes(JS::Compartment* aCompA,
+ JS::Compartment* aCompB) {
+ CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
+ CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
+ MOZ_ASSERT(apriv);
+ MOZ_ASSERT(bpriv);
+ return apriv->originInfo.mOrigin->FastSubsumes(bpriv->originInfo.mOrigin);
+}
+
+/* static */
+bool CompartmentOriginInfo::SubsumesIgnoringFPD(JS::Compartment* aCompA,
+ JS::Compartment* aCompB) {
+ CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA);
+ CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB);
+ MOZ_ASSERT(apriv);
+ MOZ_ASSERT(bpriv);
+ return apriv->originInfo.mOrigin->FastSubsumesIgnoringFPD(
+ bpriv->originInfo.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();
+ }
+}
+
+JSObject* UnprivilegedJunkScope() {
+ return XPCJSRuntime::Get()->UnprivilegedJunkScope();
+}
+
+JSObject* UnprivilegedJunkScope(const fallible_t&) {
+ return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible);
+}
+
+bool IsUnprivilegedJunkScope(JSObject* obj) {
+ return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj);
+}
+
+JSObject* NACScope(JSObject* global) {
+ // If we're a chrome global, just use ourselves.
+ if (AccessCheck::isChrome(global)) {
+ return global;
+ }
+
+ JSObject* scope = UnprivilegedJunkScope();
+ JS::ExposeObjectToActiveJS(scope);
+ return scope;
+}
+
+JSObject* PrivilegedJunkScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
+
+JSObject* CompilationScope() { return XPCJSRuntime::Get()->LoaderGlobal(); }
+
+nsGlobalWindowInner* WindowOrNull(JSObject* aObj) {
+ MOZ_ASSERT(aObj);
+ MOZ_ASSERT(!js::IsWrapper(aObj));
+
+ nsGlobalWindowInner* win = nullptr;
+ UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
+ return win;
+}
+
+nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj) {
+ MOZ_ASSERT(aObj);
+ JSObject* glob = JS::GetNonCCWObjectGlobal(aObj);
+
+ return WindowOrNull(glob);
+}
+
+JSObject* SandboxPrototypeOrNull(JSContext* aCx, JSObject* aObj) {
+ MOZ_ASSERT(aObj);
+
+ if (!IsSandbox(aObj)) {
+ return nullptr;
+ }
+
+ // Sandbox can't be a Proxy so it must have a static prototype.
+ JSObject* proto = GetStaticPrototype(aObj);
+ if (!proto || !IsSandboxPrototypeProxy(proto)) {
+ return nullptr;
+ }
+
+ return js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false);
+}
+
+nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) {
+ JSObject* glob = JS::CurrentGlobalOrNull(cx);
+ return glob ? WindowOrNull(glob) : 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
+
+static void 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);
+}
+
+static size_t CompartmentSizeOfIncludingThisCallback(
+ MallocSizeOf mallocSizeOf, JS::Compartment* compartment) {
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0;
+}
+
+/*
+ * 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.
+
+ return false;
+}
+
+void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) {
+ if (CycleCollectedJSContext* ccx = GetContext()) {
+ const auto* cx = static_cast<const XPCJSContext*>(ccx);
+ if (AutoMarkingPtr* roots = cx->mAutoRoots) {
+ roots->TraceJSAll(trc);
+ }
+ }
+
+ if (mIID2NativeInterfaceMap) {
+ mIID2NativeInterfaceMap->Trace(trc);
+ }
+
+ dom::TraceBlackJS(trc);
+}
+
+void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
+ XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
+}
+
+void XPCJSRuntime::TraverseAdditionalNativeRoots(
+ nsCycleCollectionNoteRootCallback& cb) {
+ XPCWrappedNativeScope::SuspectAllWrappers(cb);
+
+ auto* parti = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS);
+ for (auto* wjs : mSubjectToFinalizationWJS) {
+ MOZ_DIAGNOSTIC_ASSERT(wjs->IsSubjectToFinalization());
+ cb.NoteXPCOMRoot(ToSupports(wjs), parti);
+ }
+}
+
+void XPCJSRuntime::UnmarkSkippableJSHolders() {
+ CycleCollectedJSRuntime::UnmarkSkippableJSHolders();
+}
+
+void XPCJSRuntime::PrepareForForgetSkippable() {
+ nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
+ }
+}
+
+void XPCJSRuntime::BeginCycleCollectionCallback(CCReason aReason) {
+ nsJSContext::BeginCycleCollectionCallback(aReason);
+
+ nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
+ }
+}
+
+void XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
+ nsJSContext::EndCycleCollectionCallback(aResults);
+
+ nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr);
+ }
+}
+
+void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation, bool aPurge) {
+ mAsyncSnowWhiteFreer->Start(aContinuation, aPurge);
+}
+
+void xpc_UnmarkSkippableJSHolders() {
+ if (nsXPConnect::GetRuntimeInstance()) {
+ nsXPConnect::GetRuntimeInstance()->UnmarkSkippableJSHolders();
+ }
+}
+
+/* static */
+void XPCJSRuntime::GCSliceCallback(JSContext* cx, JS::GCProgress progress,
+ const JS::GCDescription& desc) {
+ XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
+ if (!self) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService();
+ if (obs) {
+ switch (progress) {
+ case JS::GC_CYCLE_BEGIN:
+ obs->NotifyObservers(nullptr, "garbage-collector-begin", nullptr);
+ break;
+ case JS::GC_CYCLE_END:
+ obs->NotifyObservers(nullptr, "garbage-collector-end", nullptr);
+ break;
+ default:
+ break;
+ }
+ }
+
+ CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN);
+
+ 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);
+ }
+}
+
+/* static */
+void XPCJSRuntime::FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status,
+ void* data) {
+ XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance();
+ if (!self) {
+ return;
+ }
+
+ switch (status) {
+ case JSFINALIZE_GROUP_PREPARE: {
+ MOZ_ASSERT(!self->mDoingFinalization, "bad state");
+
+ MOZ_ASSERT(!self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = true;
+
+ self->mDoingFinalization = true;
+
+ break;
+ }
+ case JSFINALIZE_GROUP_START: {
+ MOZ_ASSERT(self->mDoingFinalization, "bad state");
+
+ MOZ_ASSERT(self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = false;
+
+ break;
+ }
+ case JSFINALIZE_GROUP_END: {
+ MOZ_ASSERT(self->mDoingFinalization, "bad state");
+ self->mDoingFinalization = false;
+
+ break;
+ }
+ case JSFINALIZE_COLLECTION_END: {
+ MOZ_ASSERT(!self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = true;
+
+ if (CycleCollectedJSContext* ccx = self->GetContext()) {
+ const auto* 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();
+ }
+ }
+
+ XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs();
+
+ // 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();
+
+ MOZ_ASSERT(self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = false;
+
+ break;
+ }
+ }
+}
+
+/* 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;
+
+ self->mWrappedJSMap->UpdateWeakPointersAfterGC(trc);
+ self->mUAWidgetScopeMap.traceWeak(trc);
+}
+
+/* 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);
+ }
+}
+
+void CompartmentPrivate::UpdateWeakPointersAfterGC(JSTracer* trc) {
+ mRemoteProxies.traceWeak(trc);
+ mWrappedJSMap->UpdateWeakPointersAfterGC(trc);
+ mScope->UpdateWeakPointersAfterGC(trc);
+}
+
+void XPCJSRuntime::CustomOutOfMemoryCallback() {
+ if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
+ return;
+ }
+
+ nsCOMPtr<nsIMemoryInfoDumper> dumper =
+ do_GetService("@mozilla.org/memory-info-dumper;1");
+ if (!dumper) {
+ return;
+ }
+
+ // If this fails, it fails silently.
+ dumper->DumpMemoryInfoToTempDir(u"due-to-JS-OOM"_ns,
+ /* anonymize = */ false,
+ /* minimizeMemoryUsage = */ false);
+}
+
+void XPCJSRuntime::OnLargeAllocationFailure() {
+ CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reporting);
+
+ nsCOMPtr<nsIObserverService> os = xpc::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+ }
+
+ CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reported);
+}
+
+class LargeAllocationFailureRunnable final : public Runnable {
+ Mutex mMutex MOZ_UNANNOTATED;
+ CondVar mCondVar;
+ bool mWaiting;
+
+ virtual ~LargeAllocationFailureRunnable() { MOZ_ASSERT(!mWaiting); }
+
+ protected:
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ XPCJSRuntime::Get()->OnLargeAllocationFailure();
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mWaiting);
+
+ mWaiting = false;
+ mCondVar.Notify();
+ return NS_OK;
+ }
+
+ public:
+ LargeAllocationFailureRunnable()
+ : mozilla::Runnable("LargeAllocationFailureRunnable"),
+ mMutex("LargeAllocationFailureRunnable::mMutex"),
+ mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"),
+ mWaiting(true) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ void BlockUntilDone() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ MutexAutoLock lock(mMutex);
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+ }
+};
+
+static void 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.
+extern const char gToolkitBuildID[];
+
+bool mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) {
+ size_t length = std::char_traits<char>::length(gToolkitBuildID);
+ return aBuildID->append(gToolkitBuildID, length);
+}
+
+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();
+
+ mSubjectToFinalizationWJS.clear();
+
+ CycleCollectedJSRuntime::Shutdown(cx);
+}
+
+XPCJSRuntime::~XPCJSRuntime() {
+ MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
+}
+
+// If |*anonymizeID| is non-zero and this is a user realm, the name will
+// be anonymized.
+static void GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID,
+ bool replaceSlashes) {
+ if (*anonymizeID && !js::IsSystemRealm(realm)) {
+ name.AppendPrintf("<anonymized-%d>", *anonymizeID);
+ *anonymizeID += 1;
+ } else if (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.
+ static const char* 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.
+ static const char* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy=";
+ int ownedByPos = name.Find(ownedByPrefix);
+ if (ownedByPos >= 0) {
+ const char* 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");
+ }
+}
+
+extern void xpc::GetCurrentRealmName(JSContext* cx, nsCString& name) {
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!global) {
+ name.AssignLiteral("no global");
+ return;
+ }
+
+ JS::Realm* realm = GetNonCCWObjectRealm(global);
+ int anonymizeID = 0;
+ GetRealmName(realm, name, &anonymizeID, false);
+}
+
+void xpc::AddGCCallback(xpcGCCallback cb) {
+ XPCJSRuntime::Get()->AddGCCallback(cb);
+}
+
+void xpc::RemoveGCCallback(xpcGCCallback cb) {
+ XPCJSRuntime::Get()->RemoveGCCallback(cb);
+}
+
+static int64_t JSMainRuntimeGCHeapDistinguishedAmount() {
+ JSContext* cx = danger::GetJSContext();
+ return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize;
+}
+
+static int64_t JSMainRuntimeTemporaryPeakDistinguishedAmount() {
+ JSContext* cx = danger::GetJSContext();
+ return JS::PeakSizeOfTemporary(cx);
+}
+
+static int64_t JSMainRuntimeCompartmentsSystemDistinguishedAmount() {
+ JSContext* cx = danger::GetJSContext();
+ return JS::SystemCompartmentCount(cx);
+}
+
+static int64_t JSMainRuntimeCompartmentsUserDistinguishedAmount() {
+ JSContext* cx = XPCJSContext::Get()->Context();
+ return JS::UserCompartmentCount(cx);
+}
+
+static int64_t JSMainRuntimeRealmsSystemDistinguishedAmount() {
+ JSContext* cx = danger::GetJSContext();
+ return JS::SystemRealmCount(cx);
+}
+
+static int64_t JSMainRuntimeRealmsUserDistinguishedAmount() {
+ JSContext* cx = XPCJSContext::Get()->Context();
+ return JS::UserRealmCount(cx);
+}
+
+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').");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter)
+
+// 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.
+
+#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
+
+#define REPORT(_path, _kind, _units, _amount, _desc) \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::_units, _amount, \
+ nsLiteralCString(_desc), data);
+
+#define REPORT_BYTES(_path, _kind, _amount, _desc) \
+ REPORT(_path, _kind, UNITS_BYTES, _amount, _desc);
+
+#define REPORT_GC_BYTES(_path, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ gcTotal += amount; \
+ } while (0)
+
+// Report realm/zone non-GC (KIND_HEAP) bytes.
+#define ZRREPORT_BYTES(_path, _amount, _desc) \
+ do { \
+ /* Assign _descLiteral plus "" into a char* to prove that it's */ \
+ /* actually a literal. */ \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ if (amount >= SUNDRIES_THRESHOLD) { \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_HEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ } else { \
+ sundriesMallocHeap += amount; \
+ } \
+ } while (0)
+
+// Report realm/zone GC bytes.
+#define ZRREPORT_GC_BYTES(_path, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ if (amount >= SUNDRIES_THRESHOLD) { \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ gcTotal += amount; \
+ } else { \
+ sundriesGCHeap += amount; \
+ } \
+ } while (0)
+
+// Report realm/zone non-heap bytes.
+#define ZRREPORT_NONHEAP_BYTES(_path, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ if (amount >= SUNDRIES_THRESHOLD) { \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ } else { \
+ sundriesNonHeap += amount; \
+ } \
+ } while (0)
+
+// Report runtime bytes.
+#define RREPORT_BYTES(_path, _kind, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ rtTotal += amount; \
+ } while (0)
+
+// Report GC thing bytes.
+#define MREPORT_BYTES(_path, _kind, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ nsLiteralCString(_desc), data); \
+ gcThingTotal += amount; \
+ } while (0)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf)
+
+namespace xpc {
+
+static void ReportZoneStats(const JS::ZoneStats& zStats,
+ const xpc::ZoneStatsExtras& extras,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, bool anonymize,
+ size_t* gcTotalOut = nullptr) {
+ const nsCString& pathPrefix = extras.pathPrefix;
+ size_t gcTotal = 0;
+ size_t sundriesGCHeap = 0;
+ size_t sundriesMallocHeap = 0;
+ size_t sundriesNonHeap = 0;
+
+ MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
+
+ ZRREPORT_GC_BYTES(pathPrefix + "symbols/gc-heap"_ns, zStats.symbolsGCHeap,
+ "Symbols.");
+
+ ZRREPORT_GC_BYTES(
+ pathPrefix + "gc-heap-arena-admin"_ns, zStats.gcHeapArenaAdmin,
+ "Bookkeeping information and alignment padding within GC arenas.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "unused-gc-things"_ns,
+ zStats.unusedGCThings.totalSize(),
+ "Unused GC thing cells within non-empty arenas.");
+
+ ZRREPORT_BYTES(pathPrefix + "unique-id-map"_ns, zStats.uniqueIdMap,
+ "Address-independent cell identities.");
+
+ ZRREPORT_BYTES(pathPrefix + "propmap-tables"_ns, zStats.initialPropMapTable,
+ "Tables storing property map information.");
+
+ ZRREPORT_BYTES(pathPrefix + "shape-tables"_ns, zStats.shapeTables,
+ "Tables storing shape information.");
+
+ 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.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "jit-codes-gc-heap"_ns, zStats.jitCodesGCHeap,
+ "References to executable code pools used by the JITs.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "getter-setters-gc-heap"_ns,
+ zStats.getterSettersGCHeap,
+ "Information for getter/setter properties.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/compact"_ns,
+ zStats.compactPropMapsGCHeap,
+ "Information about object properties.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/normal"_ns,
+ zStats.normalPropMapsGCHeap,
+ "Information about object properties.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/dict"_ns,
+ zStats.dictPropMapsGCHeap,
+ "Information about dictionary mode object properties.");
+
+ ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/children"_ns,
+ zStats.propMapChildren, "Tables for PropMap children.");
+
+ ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/tables"_ns,
+ zStats.propMapTables, "HashTables for PropMaps.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "scopes/gc-heap"_ns, zStats.scopesGCHeap,
+ "Scope information for scripts.");
+
+ ZRREPORT_BYTES(pathPrefix + "scopes/malloc-heap"_ns, zStats.scopesMallocHeap,
+ "Arrays of binding names and other binding-related data.");
+
+ ZRREPORT_GC_BYTES(pathPrefix + "regexp-shareds/gc-heap"_ns,
+ zStats.regExpSharedsGCHeap, "Shared compiled regexp data.");
+
+ ZRREPORT_BYTES(pathPrefix + "regexp-shareds/malloc-heap"_ns,
+ zStats.regExpSharedsMallocHeap,
+ "Shared compiled regexp data.");
+
+ 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.");
+
+ size_t stringsNotableAboutMemoryGCHeap = 0;
+ size_t stringsNotableAboutMemoryMallocHeap = 0;
+
+#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);
+
+ nsDependentCString notableString(info.buffer.get());
+
+ // 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("/", "\\");
+
+ bool truncated = notableString.Length() < info.length;
+
+ nsCString path =
+ pathPrefix +
+ nsPrintfCString("strings/" STRING_LENGTH "%zu, copies=%d, \"%s\"%s)/",
+ info.length, info.numCopies, escapedString.get(),
+ truncated ? " (truncated)" : "");
+
+ if (info.gcHeapLatin1 > 0) {
+ REPORT_GC_BYTES(path + "gc-heap/latin1"_ns, info.gcHeapLatin1,
+ "Latin1 strings. " MAYBE_INLINE);
+ }
+
+ if (info.gcHeapTwoByte > 0) {
+ REPORT_GC_BYTES(path + "gc-heap/two-byte"_ns, info.gcHeapTwoByte,
+ "TwoByte strings. " MAYBE_INLINE);
+ }
+
+ if (info.mallocHeapLatin1 > 0) {
+ REPORT_BYTES(path + "malloc-heap/latin1"_ns, KIND_HEAP,
+ info.mallocHeapLatin1,
+ "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ if (info.mallocHeapTwoByte > 0) {
+ REPORT_BYTES(
+ path + "malloc-heap/two-byte"_ns, KIND_HEAP, info.mallocHeapTwoByte,
+ "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
+ }
+ }
+
+ nsCString nonNotablePath = pathPrefix;
+ nonNotablePath += (zStats.isTotals || anonymize)
+ ? "strings/"_ns
+ : "strings/string(<non-notable strings>)/"_ns;
+
+ if (zStats.stringInfo.gcHeapLatin1 > 0) {
+ REPORT_GC_BYTES(nonNotablePath + "gc-heap/latin1"_ns,
+ zStats.stringInfo.gcHeapLatin1,
+ "Latin1 strings. " MAYBE_INLINE);
+ }
+
+ if (zStats.stringInfo.gcHeapTwoByte > 0) {
+ REPORT_GC_BYTES(nonNotablePath + "gc-heap/two-byte"_ns,
+ zStats.stringInfo.gcHeapTwoByte,
+ "TwoByte strings. " MAYBE_INLINE);
+ }
+
+ if (zStats.stringInfo.mallocHeapLatin1 > 0) {
+ REPORT_BYTES(nonNotablePath + "malloc-heap/latin1"_ns, KIND_HEAP,
+ zStats.stringInfo.mallocHeapLatin1,
+ "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ if (zStats.stringInfo.mallocHeapTwoByte > 0) {
+ REPORT_BYTES(nonNotablePath + "malloc-heap/two-byte"_ns, KIND_HEAP,
+ zStats.stringInfo.mallocHeapTwoByte,
+ "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ 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.");
+ }
+
+ const JS::ShapeInfo& shapeInfo = zStats.shapeInfo;
+ if (shapeInfo.shapesGCHeapShared > 0) {
+ REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/shared"_ns,
+ shapeInfo.shapesGCHeapShared, "Shared shapes.");
+ }
+
+ 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
+}
+
+static void 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.objectsMallocHeapSlots > 0) {
+ REPORT_BYTES(path + "objects/malloc-heap/slots"_ns, KIND_HEAP,
+ classInfo.objectsMallocHeapSlots, "Non-fixed object slots.");
+ }
+
+ if (classInfo.objectsMallocHeapElementsNormal > 0) {
+ REPORT_BYTES(path + "objects/malloc-heap/elements/normal"_ns, KIND_HEAP,
+ classInfo.objectsMallocHeapElementsNormal,
+ "Normal (non-wasm) indexed elements.");
+ }
+
+ 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.objectsMallocHeapGlobalVarNamesSet > 0) {
+ REPORT_BYTES(path + "objects/malloc-heap/global-varnames-set"_ns, KIND_HEAP,
+ classInfo.objectsMallocHeapGlobalVarNamesSet,
+ "Set of global names.");
+ }
+
+ if (classInfo.objectsMallocHeapMisc > 0) {
+ REPORT_BYTES(path + "objects/malloc-heap/misc"_ns, KIND_HEAP,
+ classInfo.objectsMallocHeapMisc, "Miscellaneous object data.");
+ }
+
+ if (classInfo.objectsNonHeapElementsNormal > 0) {
+ REPORT_BYTES(path + "objects/non-heap/elements/normal"_ns, KIND_NONHEAP,
+ classInfo.objectsNonHeapElementsNormal,
+ "Memory-mapped non-shared array buffer elements.");
+ }
+
+ 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.");
+ }
+
+ if (classInfo.objectsNonHeapCodeWasm > 0) {
+ REPORT_BYTES(path + "objects/non-heap/code/wasm"_ns, KIND_NONHEAP,
+ classInfo.objectsNonHeapCodeWasm,
+ "AOT-compiled wasm/asm.js code.");
+ }
+}
+
+static void ReportRealmStats(const JS::RealmStats& realmStats,
+ const xpc::RealmStatsExtras& extras,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, size_t* gcTotalOut = nullptr) {
+ static const nsDependentCString addonPrefix("explicit/add-ons/");
+
+ size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
+ nsAutoCString realmJSPathPrefix(extras.jsPathPrefix);
+ nsAutoCString realmDOMPathPrefix(extras.domPathPrefix);
+
+ MOZ_ASSERT(!gcTotalOut == realmStats.isTotals);
+
+ nsCString nonNotablePath = realmJSPathPrefix;
+ nonNotablePath += realmStats.isTotals
+ ? "classes/"_ns
+ : "classes/class(<non-notable classes>)/"_ns;
+
+ ReportClassStats(realmStats.classInfo, nonNotablePath, handleReport, data,
+ gcTotal);
+
+ for (size_t i = 0; i < realmStats.notableClasses.length(); i++) {
+ MOZ_ASSERT(!realmStats.isTotals);
+ const JS::NotableClassInfo& classInfo = realmStats.notableClasses[i];
+
+ nsCString classPath =
+ realmJSPathPrefix +
+ nsPrintfCString("classes/class(%s)/", classInfo.className_.get());
+
+ ReportClassStats(classInfo, classPath, handleReport, data, gcTotal);
+ }
+
+ // 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.");
+ }
+
+ if (gcTotalOut) {
+ *gcTotalOut += gcTotal;
+ }
+}
+
+static void ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo,
+ const nsACString& path,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, size_t& rtTotal) {
+ if (scriptSourceInfo.misc > 0) {
+ RREPORT_BYTES(path + "misc"_ns, KIND_HEAP, scriptSourceInfo.misc,
+ "Miscellaneous data relating to JavaScript source code.");
+ }
+}
+
+void ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats,
+ const nsACString& rtPath,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, bool anonymize,
+ size_t* rtTotalOut) {
+ size_t gcTotal = 0;
+
+ for (const auto& zStats : rtStats.zoneStatsVector) {
+ const xpc::ZoneStatsExtras* extras =
+ static_cast<const xpc::ZoneStatsExtras*>(zStats.extra);
+ ReportZoneStats(zStats, *extras, handleReport, data, anonymize, &gcTotal);
+ }
+
+ for (const auto& realmStats : rtStats.realmStatsVector) {
+ const xpc::RealmStatsExtras* extras =
+ static_cast<const xpc::RealmStatsExtras*>(realmStats.extra);
+
+ ReportRealmStats(realmStats, *extras, handleReport, data, &gcTotal);
+ }
+
+ // 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.");
+
+ RREPORT_BYTES(rtPath + "runtime/interpreter-stack"_ns, KIND_HEAP,
+ rtStats.runtime.interpreterStack, "JS interpreter frames.");
+
+ RREPORT_BYTES(
+ rtPath + "runtime/shared-immutable-strings-cache"_ns, KIND_HEAP,
+ rtStats.runtime.sharedImmutableStringsCache,
+ "Immutable strings (such as JS scripts' source text) shared across all "
+ "JSRuntimes.");
+
+ RREPORT_BYTES(rtPath + "runtime/shared-intl-data"_ns, KIND_HEAP,
+ rtStats.runtime.sharedIntlData,
+ "Shared internationalization data.");
+
+ RREPORT_BYTES(rtPath + "runtime/uncompressed-source-cache"_ns, KIND_HEAP,
+ rtStats.runtime.uncompressedSourceCache,
+ "The uncompressed source code cache.");
+
+ RREPORT_BYTES(rtPath + "runtime/script-data"_ns, KIND_HEAP,
+ rtStats.runtime.scriptData,
+ "The table holding script data shared in the runtime.");
+
+ nsCString nonNotablePath =
+ rtPath +
+ nsPrintfCString(
+ "runtime/script-sources/source(scripts=%d, <non-notable files>)/",
+ rtStats.runtime.scriptSourceInfo.numScripts);
+
+ ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, nonNotablePath,
+ handleReport, data, rtTotal);
+
+ 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("/", "\\");
+ }
+
+ nsCString notablePath =
+ rtPath +
+ nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/",
+ scriptSourceInfo.numScripts, escapedFilename.get());
+
+ ReportScriptSourceStats(scriptSourceInfo, notablePath, handleReport, data,
+ rtTotal);
+ }
+
+ 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.");
+
+ RREPORT_BYTES(rtPath + "runtime/jit-lazylink"_ns, KIND_HEAP,
+ rtStats.runtime.jitLazyLink,
+ "IonMonkey compilations waiting for lazy linking.");
+
+ if (rtTotalOut) {
+ *rtTotalOut = rtTotal;
+ }
+
+ // 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;
+ };
+
+ static void RealmCallback(JSContext* cx, void* vdata, Realm* realm,
+ const JS::AutoRequireNoGC& nogc) {
+ // silently ignore OOM errors
+ Data* data = static_cast<Data*>(vdata);
+ nsCString path;
+ GetRealmName(realm, path, &data->anonymizeID, /* replaceSlashes = */ true);
+ path.Insert(js::IsSystemRealm(realm) ? "js-main-runtime-realms/system/"_ns
+ : "js-main-runtime-realms/user/"_ns,
+ 0);
+ mozilla::Unused << data->paths.append(path);
+ }
+
+ 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.
+
+ Data d;
+ d.anonymizeID = anonymize ? 1 : 0;
+ JS::IterateRealms(XPCJSContext::Get()->Context(), &d, RealmCallback);
+
+ for (auto& path : d.paths) {
+ REPORT(nsCString(path), KIND_OTHER, UNITS_COUNT, 1,
+ "A live realm in the main JSRuntime.");
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(JSMainRuntimeRealmsReporter, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf)
+
+namespace xpc {
+
+class OrphanReporter : public JS::ObjectPrivateVisitor {
+ public:
+ explicit OrphanReporter(GetISupportsFun aGetISupports)
+ : JS::ObjectPrivateVisitor(aGetISupports), mState(OrphanMallocSizeOf) {}
+
+ virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override {
+ nsCOMPtr<nsINode> node = do_QueryInterface(aSupports);
+ if (!node || node->IsInComposedDoc()) {
+ return 0;
+ }
+
+ // 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;
+ }
+
+ nsWindowSizes sizes(mState);
+ mozilla::dom::Document::AddSizeOfNodeTree(*orphanTree, sizes);
+
+ // 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();
+ }
+
+ private:
+ SizeOfState mState;
+};
+
+#ifdef DEBUG
+static bool StartsWithExplicit(nsACString& s) {
+ return StringBeginsWith(s, "explicit/"_ns);
+}
+#endif
+
+class XPCJSRuntimeStats : public JS::RuntimeStats {
+ WindowPaths* mWindowPaths;
+ WindowPaths* mTopWindowPaths;
+ int mAnonymizeID;
+
+ public:
+ XPCJSRuntimeStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths,
+ bool anonymize)
+ : JS::RuntimeStats(JSMallocSizeOf),
+ mWindowPaths(windowPaths),
+ mTopWindowPaths(topWindowPaths),
+ mAnonymizeID(anonymize ? 1 : 0) {}
+
+ ~XPCJSRuntimeStats() {
+ for (size_t i = 0; i != realmStatsVector.length(); ++i) {
+ delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra);
+ }
+
+ for (size_t i = 0; i != zoneStatsVector.length(); ++i) {
+ delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra);
+ }
+ }
+
+ virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats,
+ const JS::AutoRequireNoGC& nogc) override {
+ xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
+ extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/");
+
+ // 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-");
+ }
+ }
+ }
+ }
+
+ extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)zone);
+
+ MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));
+
+ zStats->extra = extras;
+ }
+
+ virtual void initExtraRealmStats(Realm* realm, JS::RealmStats* realmStats,
+ const JS::AutoRequireNoGC& nogc) override {
+ xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;
+ nsCString rName;
+ GetRealmName(realm, rName, &mAnonymizeID, /* replaceSlashes = */ true);
+
+ // 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 += "realm("_ns + rName + ")/"_ns;
+
+ // 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).
+
+ MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
+ MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
+
+ realmStats->extra = extras;
+ }
+};
+
+void JSReporter::CollectReports(WindowPaths* windowPaths,
+ WindowPaths* topWindowPaths,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, bool anonymize) {
+ XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance();
+
+ // 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.
+
+ XPCJSRuntimeStats rtStats(windowPaths, topWindowPaths, anonymize);
+ OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
+ JSContext* cx = XPCJSContext::Get()->Context();
+ if (!JS::CollectRuntimeStats(cx, &rtStats, &orphanReporter, anonymize)) {
+ return;
+ }
+
+ // 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;
+ }
+
+ size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf);
+
+ size_t wrappedJSSize =
+ xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
+
+ XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf);
+ XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(cx, &sizeInfo);
+
+ mozJSModuleLoader* loader = mozJSModuleLoader::Get();
+ size_t jsModuleLoaderSize =
+ loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0;
+ mozJSModuleLoader* devToolsLoader = mozJSModuleLoader::GetDevToolsLoader();
+ size_t jsDevToolsModuleLoaderSize =
+ devToolsLoader ? devToolsLoader->SizeOfIncludingThis(JSMallocSizeOf) : 0;
+
+ // This is the second step (see above). First we report stuff in the
+ // "explicit" tree, then we report other stuff.
+
+ size_t rtTotal = 0;
+ xpc::ReportJSRuntimeExplicitTreeStats(rtStats, "explicit/js-non-window/"_ns,
+ handleReport, data, anonymize,
+ &rtTotal);
+
+ // Report the sums of the realm numbers.
+ xpc::RealmStatsExtras realmExtrasTotal;
+ realmExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/realms/");
+ realmExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/");
+ ReportRealmStats(rtStats.realmTotals, realmExtrasTotal, handleReport, data);
+
+ xpc::ZoneStatsExtras zExtrasTotal;
+ zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/");
+ ReportZoneStats(rtStats.zTotals, zExtrasTotal, handleReport, data, anonymize);
+
+ // 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(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/objects"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.object,
+ "Unused object cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/strings"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.string,
+ "Unused string cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/symbols"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol,
+ "Unused symbol cells within non-empty arenas.");
+
+ REPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/shapes"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.shape,
+ "Unused shape cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape,
+ "Unused base shape cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/getter-setters"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.getterSetter,
+ "Unused getter-setter cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/property-maps"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.propMap,
+ "Unused property map cells within non-empty arenas.");
+
+ REPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/scopes"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.scope,
+ "Unused scope cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/scripts"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.script,
+ "Unused script cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode,
+ "Unused jitcode cells within non-empty arenas.");
+
+ REPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared,
+ "Unused regexpshared 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'.");
+
+ size_t gcThingTotal = 0;
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/objects"),
+ KIND_OTHER, rtStats.realmTotals.classInfo.objectsGCHeap,
+ "Used object cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/strings"),
+ KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(),
+ "Used string cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/symbols"),
+ KIND_OTHER, rtStats.zTotals.symbolsGCHeap,
+ "Used symbol cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/shapes"),
+ KIND_OTHER,
+ rtStats.zTotals.shapeInfo.shapesGCHeapShared +
+ rtStats.zTotals.shapeInfo.shapesGCHeapDict,
+ "Used shape cells.");
+
+ MREPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"),
+ KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase,
+ "Used base shape cells.");
+
+ MREPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/getter-setters"),
+ KIND_OTHER, rtStats.zTotals.getterSettersGCHeap,
+ "Used getter/setter cells.");
+
+ MREPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/property-maps"),
+ KIND_OTHER,
+ rtStats.zTotals.dictPropMapsGCHeap +
+ rtStats.zTotals.compactPropMapsGCHeap +
+ rtStats.zTotals.normalPropMapsGCHeap,
+ "Used property map cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/scopes"),
+ KIND_OTHER, rtStats.zTotals.scopesGCHeap, "Used scope cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/scripts"),
+ KIND_OTHER, rtStats.realmTotals.scriptsGCHeap,
+ "Used script cells.");
+
+ MREPORT_BYTES(nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/jitcode"),
+ KIND_OTHER, rtStats.zTotals.jitCodesGCHeap,
+ "Used jitcode cells.");
+
+ MREPORT_BYTES(
+ nsLiteralCString(
+ "js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"),
+ KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap,
+ "Used regexpshared cells.");
+
+ MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings);
+ (void)gcThingTotal;
+
+ // 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/xpconnect/scopes"_ns, KIND_HEAP,
+ sizeInfo.mScopeAndMapSize, "XPConnect scopes.");
+
+ REPORT_BYTES("explicit/xpconnect/proto-iface-cache"_ns, KIND_HEAP,
+ sizeInfo.mProtoAndIfaceCacheSize,
+ "Prototype and interface binding caches.");
+
+ REPORT_BYTES("explicit/xpconnect/js-module-loader"_ns, KIND_HEAP,
+ jsModuleLoaderSize, "XPConnect's JS module loader.");
+ REPORT_BYTES("explicit/xpconnect/js-devtools-module-loader"_ns, KIND_HEAP,
+ jsDevToolsModuleLoaderSize, "DevTools's JS module loader.");
+
+ // Report HelperThreadState.
+
+ 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.");
+}
+
+static nsresult JSSizeOfTab(JSObject* objArg, size_t* jsObjectsSize,
+ size_t* jsStringsSize, size_t* jsPrivateSize,
+ size_t* jsOtherSize) {
+ JSContext* cx = XPCJSContext::Get()->Context();
+ JS::RootedObject obj(cx, objArg);
+
+ TabSizes sizes;
+ OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
+ NS_ENSURE_TRUE(
+ JS::AddSizeOfTab(cx, obj, moz_malloc_size_of, &orphanReporter, &sizes),
+ NS_ERROR_OUT_OF_MEMORY);
+
+ *jsObjectsSize = sizes.objects_;
+ *jsStringsSize = sizes.strings_;
+ *jsPrivateSize = sizes.private_;
+ *jsOtherSize = sizes.other_;
+ return NS_OK;
+}
+
+} // namespace xpc
+
+static void AccumulateTelemetryCallback(JSMetric id, uint32_t sample) {
+ // clang-format off
+ switch (id) {
+#define CASE_ACCUMULATE(NAME, _) \
+ case JSMetric::NAME: \
+ Telemetry::Accumulate(Telemetry::NAME, sample); \
+ break;
+
+ FOR_EACH_JS_METRIC(CASE_ACCUMULATE)
+#undef CASE_ACCUMULATE
+
+ default:
+ MOZ_CRASH("Bad metric id");
+ }
+ // clang-format on
+
+ switch (id) {
+ case JSMetric::DESERIALIZE_BYTES:
+ glean::performance_clone_deserialize::size.Accumulate(sample);
+ break;
+ case JSMetric::DESERIALIZE_ITEMS:
+ glean::performance_clone_deserialize::items.AccumulateSamples({sample});
+ break;
+ case JSMetric::DESERIALIZE_US:
+ glean::performance_clone_deserialize::time.AccumulateRawDuration(
+ TimeDuration::FromMicroseconds(sample));
+ break;
+ 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;
+ default:
+ // The rest aren't relayed to Glean.
+ break;
+ }
+}
+
+static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) {
+ switch (counter) {
+ case JSUseCounter::ASMJS:
+ SetUseCounter(obj, eUseCounter_custom_JS_asmjs);
+ break;
+ case JSUseCounter::WASM:
+ SetUseCounter(obj, eUseCounter_custom_JS_wasm);
+ break;
+ case JSUseCounter::WASM_LEGACY_EXCEPTIONS:
+ SetUseCounter(obj, eUseCounter_custom_JS_wasm_legacy_exceptions);
+ break;
+ case JSUseCounter::LATE_WEEKDAY:
+ SetUseCounter(obj, eUseCounter_custom_JS_late_weekday);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id");
+ }
+}
+
+static void 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);
+}
+
+static void 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);
+}
+
+static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) {
+ MOZ_ASSERT(cx);
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
+
+ if (!mozilla::dom::TryPreserveWrapper(obj)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mozilla::dom::HasReleasedWrapper(obj),
+ "There should be no released wrapper since we just preserved it");
+
+ return true;
+}
+
+static nsresult ReadSourceFromFilename(JSContext* cx, const char* 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.
+ const char* 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);
+
+ nsCOMPtr<nsIChannel> scriptChannel;
+ rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only allow local reading.
+ nsCOMPtr<nsIURI> actualUri;
+ rv = scriptChannel->GetURI(getter_AddRefs(actualUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString scheme;
+ rv = actualUri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) {
+ return NS_OK;
+ }
+
+ // Explicitly set the content type so that we don't load the
+ // exthandler to guess it.
+ scriptChannel->SetContentType("text/plain"_ns);
+
+ nsCOMPtr<nsIInputStream> scriptStream;
+ rv = scriptChannel->Open(getter_AddRefs(scriptStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t rawLen;
+ rv = scriptStream->Available(&rawLen);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!rawLen) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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<const unsigned char*>(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, const char* 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");
+
+ *length = 0;
+ if (twoByteSource) {
+ *twoByteSource = nullptr;
+ } else {
+ *utf8Source = nullptr;
+ }
+
+ if (!nsContentUtils::IsSystemCaller(cx)) {
+ return true;
+ }
+
+ if (!filename) {
+ return true;
+ }
+
+ nsresult rv =
+ ReadSourceFromFilename(cx, filename, twoByteSource, utf8Source, length);
+ if (NS_FAILED(rv)) {
+ xpc::Throw(cx, rv);
+ return false;
+ }
+
+ return true;
+ }
+};
+
+static const JSWrapObjectCallbacks WrapObjectCallbacks = {
+ xpc::WrapperFactory::Rewrap, xpc::WrapperFactory::PrepareForWrapping};
+
+XPCJSRuntime::XPCJSRuntime(JSContext* aCx)
+ : CycleCollectedJSRuntime(aCx),
+ mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()),
+ mIID2NativeInterfaceMap(mozilla::MakeUnique<IID2NativeInterfaceMap>()),
+ mClassInfo2NativeSetMap(mozilla::MakeUnique<ClassInfo2NativeSetMap>()),
+ mNativeSetMap(mozilla::MakeUnique<NativeSetMap>()),
+ mGCIsRunning(false),
+ mDoingFinalization(false),
+ mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) {
+ MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
+}
+
+/* static */
+XPCJSRuntime* XPCJSRuntime::Get() { return nsXPConnect::GetRuntimeInstance(); }
+
+// 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) {}
+
+ public:
+ static void construct(void* storage, JSObject* ptr) {
+ new (storage) ReflectorNode(ptr);
+ }
+ js::UniquePtr<JS::ubi::EdgeRange> edges(JSContext* cx,
+ bool wantNames) const override;
+};
+
+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());
+}
+
+} // Namespace ubi
+} // Namespace JS
+
+void ConstructUbiNode(void* storage, JSObject* ptr) {
+ JS::ubi::ReflectorNode::construct(storage, ptr);
+}
+
+void XPCJSRuntime::Initialize(JSContext* cx) {
+ mLoaderGlobal.init(cx, nullptr);
+
+ // these jsids filled in later when we have a JSContext to work with.
+ mStrIDs[0] = JS::PropertyKey::Void();
+
+ nsScriptSecurityManager::GetScriptSecurityManager()->InitJSCallbacks(cx);
+
+ // 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);
+
+ JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback);
+ JS_SetSizeOfIncludingThisCompartmentCallback(
+ cx, CompartmentSizeOfIncludingThisCallback);
+ JS::SetDestroyRealmCallback(cx, DestroyRealm);
+ JS::SetRealmNameCallback(cx, GetRealmNameCallback);
+ mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback);
+ mPrevDoCycleCollectionCallback =
+ JS::SetDoCycleCollectionCallback(cx, DoCycleCollectionCallback);
+ JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
+ JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this);
+ JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback,
+ this);
+ JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
+ if (XRE_IsE10sParentProcess()) {
+ JS::SetFilenameValidationCallback(
+ nsContentSecurityUtils::ValidateScriptFilename);
+ }
+ js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper);
+ JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
+ JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
+ JS_SetSetUseCounterCallback(cx, SetUseCounterCallback);
+
+ js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
+
+ JS::SetXrayJitInfo(&gXrayJitInfo);
+ JS::SetProcessLargeAllocationFailureCallback(
+ OnLargeAllocationFailureCallback);
+
+ // 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));
+
+ // Register memory reporters and distinguished amount functions.
+ RegisterStrongMemoryReporter(new JSMainRuntimeRealmsReporter());
+ RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
+ RegisterJSMainRuntimeGCHeapDistinguishedAmount(
+ JSMainRuntimeGCHeapDistinguishedAmount);
+ RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(
+ JSMainRuntimeTemporaryPeakDistinguishedAmount);
+ RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(
+ JSMainRuntimeCompartmentsSystemDistinguishedAmount);
+ RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(
+ JSMainRuntimeCompartmentsUserDistinguishedAmount);
+ RegisterJSMainRuntimeRealmsSystemDistinguishedAmount(
+ JSMainRuntimeRealmsSystemDistinguishedAmount);
+ RegisterJSMainRuntimeRealmsUserDistinguishedAmount(
+ JSMainRuntimeRealmsUserDistinguishedAmount);
+ mozilla::RegisterJSSizeOfTab(JSSizeOfTab);
+
+ // 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();
+ return false;
+ }
+ mStrIDs[i] = PropertyKey::fromPinnedString(str);
+ }
+
+ if (!mozilla::dom::DefineStaticJSVals(cx)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const JSClass* clasp,
+ char (&name)[72]) const {
+ if (clasp != &XPC_WN_Proto_JSClass) {
+ return false;
+ }
+
+ 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) {
+ return false;
+ }
+
+ SprintfLiteral(name, "JS Object (%s - %s)", clasp->name,
+ scr->GetJSClass()->name);
+ return true;
+}
+
+bool XPCJSRuntime::NoteCustomGCThingXPCOMChildren(
+ const JSClass* clasp, JSObject* obj,
+ nsCycleCollectionTraversalCallback& cb) const {
+ if (clasp != &XPC_WN_Tearoff_JSClass) {
+ return false;
+ }
+
+ // 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());
+ return true;
+}
+
+/***************************************************************************/
+
+void XPCJSRuntime::DebugDump(int16_t depth) {
+#ifdef DEBUG
+ depth--;
+ XPC_LOG_ALWAYS(("XPCJSRuntime @ %p", this));
+ XPC_LOG_INDENT();
+
+ // iterate wrappers...
+ XPC_LOG_ALWAYS(("mWrappedJSMap @ %p with %d wrappers(s)", mWrappedJSMap.get(),
+ mWrappedJSMap->Count()));
+ if (depth && mWrappedJSMap->Count()) {
+ XPC_LOG_INDENT();
+ mWrappedJSMap->Dump(depth);
+ XPC_LOG_OUTDENT();
+ }
+
+ XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %p with %d interface(s)",
+ mIID2NativeInterfaceMap.get(),
+ mIID2NativeInterfaceMap->Count()));
+
+ XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %p with %d sets(s)",
+ mClassInfo2NativeSetMap.get(),
+ mClassInfo2NativeSetMap->Count()));
+
+ XPC_LOG_ALWAYS(("mNativeSetMap @ %p with %d sets(s)", mNativeSetMap.get(),
+ mNativeSetMap->Count()));
+
+ // iterate sets...
+ if (depth && mNativeSetMap->Count()) {
+ XPC_LOG_INDENT();
+ for (auto i = mNativeSetMap->Iter(); !i.done(); i.next()) {
+ i.get()->DebugDump(depth);
+ }
+ XPC_LOG_OUTDENT();
+ }
+
+ XPC_LOG_OUTDENT();
+#endif
+}
+
+/***************************************************************************/
+
+void XPCJSRuntime::AddGCCallback(xpcGCCallback cb) {
+ MOZ_ASSERT(cb, "null callback");
+ extraGCCallbacks.AppendElement(cb);
+}
+
+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.
+ }
+
+ SandboxOptions options;
+ options.sandboxName.AssignLiteral("UA Widget Scope");
+ options.wantXrays = false;
+ options.wantComponents = false;
+ options.isUAWidgetScope = true;
+
+ // 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());
+
+ // Create the sandbox.
+ RootedValue v(cx);
+ nsresult rv = CreateSandboxObject(
+ cx, &v, static_cast<nsIExpandedPrincipal*>(ep), options);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ scope = &v.toObject();
+
+ JSObject* unwrapped = js::UncheckedUnwrap(scope);
+ MOZ_ASSERT(xpc::IsInUAWidgetScope(unwrapped));
+
+ MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, unwrapped));
+ } while (false);
+
+ return scope;
+}
+
+JSObject* XPCJSRuntime::UnprivilegedJunkScope(const mozilla::fallible_t&) {
+ if (!mUnprivilegedJunkScope) {
+ dom::AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ SandboxOptions options;
+ options.sandboxName.AssignLiteral("XPConnect Junk Compartment");
+ options.invisibleToDebugger = true;
+
+ RootedValue sandbox(cx);
+ nsresult rv = CreateSandboxObject(cx, &sandbox, nullptr, options);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mUnprivilegedJunkScope =
+ SandboxPrivate::GetPrivate(sandbox.toObjectOrNull());
+ }
+ MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(),
+ "Wrapper should have same lifetime as weak reference");
+ return mUnprivilegedJunkScope->GetWrapper();
+}
+
+JSObject* XPCJSRuntime::UnprivilegedJunkScope() {
+ JSObject* scope = UnprivilegedJunkScope(fallible);
+ MOZ_RELEASE_ASSERT(scope);
+ return scope;
+}
+
+bool XPCJSRuntime::IsUnprivilegedJunkScope(JSObject* obj) {
+ return mUnprivilegedJunkScope && obj == 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;
+ }
+ mLoaderGlobal = nullptr;
+}
+
+JSObject* XPCJSRuntime::LoaderGlobal() {
+ if (!mLoaderGlobal) {
+ RefPtr loader = mozJSModuleLoader::Get();
+
+ dom::AutoJSAPI jsapi;
+ jsapi.Init();
+
+ mLoaderGlobal = loader->GetSharedGlobal(jsapi.cx());
+ MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(jsapi.cx()));
+ }
+ return mLoaderGlobal;
+}
+
+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);
+}