summaryrefslogtreecommitdiff
path: root/js/xpconnect/src/XPCJSContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/XPCJSContext.cpp')
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp3771
1 files changed, 3771 insertions, 0 deletions
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
new file mode 100644
index 0000000000..6981b525c5
--- /dev/null
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -0,0 +1,3771 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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 JSContext object */
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+#include "xpcprivate.h"
+#include "xpcpublic.h"
+#include "XPCWrapper.h"
+#include "XPCJSMemoryReporter.h"
+#include "WrapperFactory.h"
+#include "mozJSComponentLoader.h"
+#include "nsAutoPtr.h"
+#include "nsNetUtil.h"
+
+#include "nsIMemoryInfoDumper.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserverService.h"
+#include "nsIDebug2.h"
+#include "nsIDocShell.h"
+#include "nsIRunnable.h"
+#include "amIAddonManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "nsContentUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsCycleCollector.h"
+#include "nsScriptLoader.h"
+#include "jsapi.h"
+#include "jsprf.h"
+#include "js/MemoryMetrics.h"
+#include "mozilla/dom/GeneratedAtomList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "AccessCheck.h"
+#include "nsGlobalWindow.h"
+#include "nsAboutProtocolUtils.h"
+
+#include "GeckoProfiler.h"
+#include "nsIXULRuntime.h"
+#include "nsJSPrincipals.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#if defined(MOZ_JEMALLOC4)
+#include "mozmemory.h"
+#endif
+
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+using namespace mozilla;
+using namespace xpc;
+using namespace JS;
+using mozilla::dom::PerThreadAtomCache;
+using mozilla::dom::AutoEntryScript;
+
+/***************************************************************************/
+
+const char* const XPCJSContext::mStrings[] = {
+ "constructor", // IDX_CONSTRUCTOR
+ "toString", // IDX_TO_STRING
+ "toSource", // IDX_TO_SOURCE
+ "lastResult", // IDX_LAST_RESULT
+ "returnCode", // IDX_RETURN_CODE
+ "value", // IDX_VALUE
+ "QueryInterface", // IDX_QUERY_INTERFACE
+ "Components", // IDX_COMPONENTS
+ "wrappedJSObject", // IDX_WRAPPED_JSOBJECT
+ "Object", // IDX_OBJECT
+ "Function", // IDX_FUNCTION
+ "prototype", // IDX_PROTOTYPE
+ "createInstance", // IDX_CREATE_INSTANCE
+ "item", // IDX_ITEM
+ "__proto__", // IDX_PROTO
+ "__iterator__", // IDX_ITERATOR
+ "__exposedProps__", // IDX_EXPOSEDPROPS
+ "eval", // IDX_EVAL
+ "controllers", // IDX_CONTROLLERS
+ "realFrameElement", // IDX_REALFRAMEELEMENT
+ "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
+ "lastIndex" // IDX_LASTINDEX
+};
+
+/***************************************************************************/
+
+static mozilla::Atomic<bool> sDiscardSystemSource(false);
+
+bool
+xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
+
+#ifdef DEBUG
+static mozilla::Atomic<bool> sExtraWarningsForSystemJS(false);
+bool xpc::ExtraWarningsForSystemJS() { return sExtraWarningsForSystemJS; }
+#else
+bool xpc::ExtraWarningsForSystemJS() { return false; }
+#endif
+
+static mozilla::Atomic<bool> sSharedMemoryEnabled(false);
+
+bool
+xpc::SharedMemoryEnabled() { return sSharedMemoryEnabled; }
+
+// *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
+ {
+ TimeStamp start = TimeStamp::Now();
+ bool hadSnowWhiteObjects = nsCycleCollector_doDeferredDeletion();
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING,
+ uint32_t((TimeStamp::Now() - start).ToMilliseconds()));
+ if (hadSnowWhiteObjects && !mContinuation) {
+ mContinuation = true;
+ if (NS_FAILED(NS_DispatchToCurrentThread(this))) {
+ mActive = false;
+ }
+ } else {
+#if defined(MOZ_JEMALLOC4)
+ if (mPurge) {
+ /* Jemalloc purges dirty pages regularly during free() when the
+ * ratio of dirty pages compared to active pages is higher than
+ * 1 << lg_dirty_mult. A high ratio can have an impact on
+ * performance, so we use the default ratio of 8, but force a
+ * regular purge of all remaining dirty pages, after cycle
+ * collection. */
+ Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer;
+ jemalloc_free_dirty_pages();
+ }
+#endif
+ mActive = false;
+ }
+ return NS_OK;
+ }
+
+ void Dispatch(bool aContinuation = false, bool aPurge = false)
+ {
+ if (mContinuation) {
+ mContinuation = aContinuation;
+ }
+ mPurge = aPurge;
+ if (!mActive && NS_SUCCEEDED(NS_DispatchToCurrentThread(this))) {
+ mActive = true;
+ }
+ }
+
+ AsyncFreeSnowWhite() : mContinuation(false), mActive(false), mPurge(false) {}
+
+public:
+ bool mContinuation;
+ bool mActive;
+ bool mPurge;
+};
+
+namespace xpc {
+
+CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
+ : wantXrays(false)
+ , allowWaivers(true)
+ , writeToGlobalPrototype(false)
+ , skipWriteToGlobalPrototype(false)
+ , isWebExtensionContentScript(false)
+ , waiveInterposition(false)
+ , allowCPOWs(false)
+ , universalXPConnectEnabled(false)
+ , forcePermissiveCOWs(false)
+ , scriptability(c)
+ , scope(nullptr)
+ , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
+{
+ MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
+ mozilla::PodArrayZero(wrapperDenialWarnings);
+}
+
+CompartmentPrivate::~CompartmentPrivate()
+{
+ MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
+ mWrappedJSMap->ShutdownMarker();
+ delete mWrappedJSMap;
+}
+
+static bool
+TryParseLocationURICandidate(const nsACString& uristr,
+ CompartmentPrivate::LocationHint aLocationHint,
+ nsIURI** aURI)
+{
+ static NS_NAMED_LITERAL_CSTRING(kGRE, "resource://gre/");
+ static NS_NAMED_LITERAL_CSTRING(kToolkit, "chrome://global/");
+ static NS_NAMED_LITERAL_CSTRING(kBrowser, "chrome://browser/");
+
+ if (aLocationHint == CompartmentPrivate::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, NS_LITERAL_CSTRING("xb")))
+ 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 CompartmentPrivate::TryParseLocationURI(CompartmentPrivate::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:
+ // <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.
+ // <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, such as the add-on SDK.
+
+ 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_LITERAL_CSTRING(":"));
+ 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 (nsXPConnect::SecurityManager()->IsSystemPrincipal(aPrincipal))
+ return true;
+
+ // nsExpandedPrincipal gets a free pass.
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ if (ep)
+ return true;
+
+ // Check whether our URI is an "about:" URI that allows scripts. If it is,
+ // we need to allow JS to run.
+ nsCOMPtr<nsIURI> principalURI;
+ aPrincipal->GetURI(getter_AddRefs(principalURI));
+ MOZ_ASSERT(principalURI);
+ bool isAbout;
+ nsresult rv = principalURI->SchemeIs("about", &isAbout);
+ if (NS_SUCCEEDED(rv) && isAbout) {
+ nsCOMPtr<nsIAboutModule> module;
+ rv = NS_GetAboutModule(principalURI, getter_AddRefs(module));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t flags;
+ rv = module->GetURIFlags(principalURI, &flags);
+ if (NS_SUCCEEDED(rv) &&
+ (flags & nsIAboutModule::ALLOW_SCRIPT)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+Scriptability::Scriptability(JSCompartment* c) : mScriptBlocks(0)
+ , mDocShellAllowsScript(true)
+ , mScriptBlockedByPolicy(false)
+{
+ nsIPrincipal* prin = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c));
+ mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin);
+
+ // If we're not immune, we should have a real principal with a codebase URI.
+ // Check the URI against the new-style domain policy.
+ if (!mImmuneToScriptPolicy) {
+ nsCOMPtr<nsIURI> codebase;
+ nsresult rv = prin->GetURI(getter_AddRefs(codebase));
+ bool policyAllows;
+ if (NS_SUCCEEDED(rv) && codebase &&
+ NS_SUCCEEDED(nsXPConnect::SecurityManager()->PolicyAllowsScript(codebase, &policyAllows)))
+ {
+ mScriptBlockedByPolicy = !policyAllows;
+ } else {
+ // Something went wrong - be safe and block script.
+ mScriptBlockedByPolicy = true;
+ }
+ }
+}
+
+bool
+Scriptability::Allowed()
+{
+ return mDocShellAllowsScript && !mScriptBlockedByPolicy &&
+ mScriptBlocks == 0;
+}
+
+bool
+Scriptability::IsImmuneToScriptPolicy()
+{
+ return mImmuneToScriptPolicy;
+}
+
+void
+Scriptability::Block()
+{
+ ++mScriptBlocks;
+}
+
+void
+Scriptability::Unblock()
+{
+ MOZ_ASSERT(mScriptBlocks > 0);
+ --mScriptBlocks;
+}
+
+void
+Scriptability::SetDocShellAllowsScript(bool aAllowed)
+{
+ mDocShellAllowsScript = aAllowed || mImmuneToScriptPolicy;
+}
+
+/* static */
+Scriptability&
+Scriptability::Get(JSObject* aScope)
+{
+ return CompartmentPrivate::Get(aScope)->scriptability;
+}
+
+bool
+IsContentXBLScope(JSCompartment* compartment)
+{
+ // We always eagerly create compartment privates for XBL scopes.
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ if (!priv || !priv->scope)
+ return false;
+ return priv->scope->IsContentXBLScope();
+}
+
+bool
+IsInContentXBLScope(JSObject* obj)
+{
+ return IsContentXBLScope(js::GetObjectCompartment(obj));
+}
+
+bool
+IsInAddonScope(JSObject* obj)
+{
+ return ObjectScope(obj)->IsAddonScope();
+}
+
+bool
+IsUniversalXPConnectEnabled(JSCompartment* compartment)
+{
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ if (!priv)
+ return false;
+ return priv->universalXPConnectEnabled;
+}
+
+bool
+IsUniversalXPConnectEnabled(JSContext* cx)
+{
+ JSCompartment* compartment = js::GetContextCompartment(cx);
+ if (!compartment)
+ return false;
+ return IsUniversalXPConnectEnabled(compartment);
+}
+
+bool
+EnableUniversalXPConnect(JSContext* cx)
+{
+ JSCompartment* compartment = js::GetContextCompartment(cx);
+ if (!compartment)
+ return true;
+ // Never set universalXPConnectEnabled on a chrome compartment - it confuses
+ // the security wrapping code.
+ if (AccessCheck::isChrome(compartment))
+ return true;
+ CompartmentPrivate* priv = CompartmentPrivate::Get(compartment);
+ if (!priv)
+ return true;
+ if (priv->universalXPConnectEnabled)
+ return true;
+ priv->universalXPConnectEnabled = true;
+
+ // Recompute all the cross-compartment wrappers leaving the newly-privileged
+ // compartment.
+ bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment),
+ js::AllCompartments());
+ NS_ENSURE_TRUE(ok, false);
+
+ // The Components object normally isn't defined for unprivileged web content,
+ // but we define it when UniversalXPConnect is enabled to support legacy
+ // tests.
+ XPCWrappedNativeScope* scope = priv->scope;
+ if (!scope)
+ return true;
+ scope->ForcePrivilegedComponents();
+ return scope->AttachComponentsObject(cx);
+}
+
+JSObject*
+UnprivilegedJunkScope()
+{
+ return XPCJSContext::Get()->UnprivilegedJunkScope();
+}
+
+JSObject*
+PrivilegedJunkScope()
+{
+ return XPCJSContext::Get()->PrivilegedJunkScope();
+}
+
+JSObject*
+CompilationScope()
+{
+ return XPCJSContext::Get()->CompilationScope();
+}
+
+nsGlobalWindow*
+WindowOrNull(JSObject* aObj)
+{
+ MOZ_ASSERT(aObj);
+ MOZ_ASSERT(!js::IsWrapper(aObj));
+
+ nsGlobalWindow* win = nullptr;
+ UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win);
+ return win;
+}
+
+nsGlobalWindow*
+WindowGlobalOrNull(JSObject* aObj)
+{
+ MOZ_ASSERT(aObj);
+ JSObject* glob = js::GetGlobalForObjectCrossCompartment(aObj);
+
+ return WindowOrNull(glob);
+}
+
+nsGlobalWindow*
+AddonWindowOrNull(JSObject* aObj)
+{
+ if (!IsInAddonScope(aObj))
+ return nullptr;
+
+ JSObject* global = js::GetGlobalForObjectCrossCompartment(aObj);
+ JSObject* proto = js::GetPrototypeNoProxy(global);
+
+ // Addons could theoretically change the prototype of the addon scope, but
+ // we pretty much just want to crash if that happens so that we find out
+ // about it and get them to change their code.
+ MOZ_RELEASE_ASSERT(js::IsCrossCompartmentWrapper(proto) ||
+ xpc::IsSandboxPrototypeProxy(proto));
+ JSObject* mainGlobal = js::UncheckedUnwrap(proto, /* stopAtWindowProxy = */ false);
+ MOZ_RELEASE_ASSERT(JS_IsGlobalObject(mainGlobal));
+
+ return WindowOrNull(mainGlobal);
+}
+
+nsGlobalWindow*
+CurrentWindowOrNull(JSContext* cx)
+{
+ JSObject* glob = JS::CurrentGlobalOrNull(cx);
+ return glob ? WindowOrNull(glob) : nullptr;
+}
+
+} // namespace xpc
+
+static void
+CompartmentDestroyedCallback(JSFreeOp* fop, JSCompartment* compartment)
+{
+ // NB - This callback may be called in JS_DestroyContext, which happens
+ // after the XPCJSContext has been torn down.
+
+ // Get the current compartment private into an AutoPtr (which will do the
+ // cleanup for us), and null out the private (which may already be null).
+ nsAutoPtr<CompartmentPrivate> priv(CompartmentPrivate::Get(compartment));
+ JS_SetCompartmentPrivate(compartment, nullptr);
+}
+
+static size_t
+CompartmentSizeOfIncludingThisCallback(MallocSizeOf mallocSizeOf, JSCompartment* 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 XPCJSContext::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 XPCJSContext::TraceNativeBlackRoots(JSTracer* trc)
+{
+ if (AutoMarkingPtr* roots = Get()->mAutoRoots)
+ roots->TraceJSAll(trc);
+
+ // XPCJSObjectHolders don't participate in cycle collection, so always
+ // trace them here.
+ XPCRootSetElem* e;
+ for (e = mObjectHolderRoots; e; e = e->GetNextRoot())
+ static_cast<XPCJSObjectHolder*>(e)->TraceJS(trc);
+
+ dom::TraceBlackJS(trc, JS_GetGCParameter(Context(), JSGC_NUMBER),
+ nsXPConnect::XPConnect()->IsShuttingDown());
+}
+
+void XPCJSContext::TraceAdditionalNativeGrayRoots(JSTracer* trc)
+{
+ XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc, this);
+
+ for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot())
+ static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
+
+ for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot())
+ static_cast<nsXPCWrappedJS*>(e)->TraceJS(trc);
+}
+
+void
+XPCJSContext::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb)
+{
+ XPCWrappedNativeScope::SuspectAllWrappers(this, cb);
+
+ for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot()) {
+ XPCTraceableVariant* v = static_cast<XPCTraceableVariant*>(e);
+ if (nsCCUncollectableMarker::InGeneration(cb,
+ v->CCGeneration())) {
+ JS::Value val = v->GetJSValPreserveColor();
+ if (val.isObject() && !JS::ObjectIsMarkedGray(&val.toObject()))
+ continue;
+ }
+ cb.NoteXPCOMRoot(v);
+ }
+
+ for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot()) {
+ cb.NoteXPCOMRoot(ToSupports(static_cast<nsXPCWrappedJS*>(e)));
+ }
+}
+
+void
+XPCJSContext::UnmarkSkippableJSHolders()
+{
+ CycleCollectedJSContext::UnmarkSkippableJSHolders();
+}
+
+void
+XPCJSContext::PrepareForForgetSkippable()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
+ }
+}
+
+void
+XPCJSContext::BeginCycleCollectionCallback()
+{
+ nsJSContext::BeginCycleCollectionCallback();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
+ }
+}
+
+void
+XPCJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults)
+{
+ nsJSContext::EndCycleCollectionCallback(aResults);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr);
+ }
+}
+
+void
+XPCJSContext::DispatchDeferredDeletion(bool aContinuation, bool aPurge)
+{
+ mAsyncSnowWhiteFreer->Dispatch(aContinuation, aPurge);
+}
+
+void
+xpc_UnmarkSkippableJSHolders()
+{
+ if (nsXPConnect::XPConnect()->GetContext()) {
+ nsXPConnect::XPConnect()->GetContext()->UnmarkSkippableJSHolders();
+ }
+}
+
+/* static */ void
+XPCJSContext::GCSliceCallback(JSContext* cx,
+ JS::GCProgress progress,
+ const JS::GCDescription& desc)
+{
+ XPCJSContext* self = nsXPConnect::GetContextInstance();
+ if (!self)
+ return;
+
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN ||
+ progress == JS::GC_SLICE_BEGIN);
+#endif
+
+ if (self->mPrevGCSliceCallback)
+ (*self->mPrevGCSliceCallback)(cx, progress, desc);
+}
+
+/* static */ void
+XPCJSContext::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.
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction([](){nsJSContext::CycleCollectNow(nullptr);}));
+
+ XPCJSContext* self = nsXPConnect::GetContextInstance();
+ if (!self)
+ return;
+
+ if (self->mPrevDoCycleCollectionCallback)
+ (*self->mPrevDoCycleCollectionCallback)(cx);
+}
+
+void
+XPCJSContext::CustomGCCallback(JSGCStatus status)
+{
+ nsTArray<xpcGCCallback> callbacks(extraGCCallbacks);
+ for (uint32_t i = 0; i < callbacks.Length(); ++i)
+ callbacks[i](status);
+}
+
+/* static */ void
+XPCJSContext::FinalizeCallback(JSFreeOp* fop,
+ JSFinalizeStatus status,
+ bool isZoneGC,
+ void* data)
+{
+ XPCJSContext* self = nsXPConnect::GetContextInstance();
+ if (!self)
+ return;
+
+ switch (status) {
+ case JSFINALIZE_GROUP_START:
+ {
+ MOZ_ASSERT(!self->mDoingFinalization, "bad state");
+
+ MOZ_ASSERT(!self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = true;
+
+ self->mDoingFinalization = true;
+ break;
+ }
+ case JSFINALIZE_GROUP_END:
+ {
+ MOZ_ASSERT(self->mDoingFinalization, "bad state");
+ self->mDoingFinalization = false;
+
+ // Sweep scopes needing cleanup
+ XPCWrappedNativeScope::KillDyingScopes();
+
+ MOZ_ASSERT(self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = false;
+
+ break;
+ }
+ case JSFINALIZE_COLLECTION_END:
+ {
+ MOZ_ASSERT(!self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = true;
+
+ if (AutoMarkingPtr* roots = Get()->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 = XPCJSContext::Get()->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 transfered these native objects to this table when their
+ // JSObject's 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 where 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 where not themselves
+ // slated for finalization in this gc cycle. So... 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.
+
+ for (auto i = self->mDyingWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<XPCWrappedNativeProtoMap::Entry*>(i.Get());
+ delete static_cast<const XPCWrappedNativeProto*>(entry->key);
+ i.Remove();
+ }
+
+ MOZ_ASSERT(self->mGCIsRunning, "bad state");
+ self->mGCIsRunning = false;
+
+ break;
+ }
+ }
+}
+
+/* static */ void
+XPCJSContext::WeakPointerZoneGroupCallback(JSContext* cx, 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.
+ XPCJSContext* self = static_cast<XPCJSContext*>(data);
+
+ self->mWrappedJSMap->UpdateWeakPointersAfterGC(self);
+
+ XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self);
+}
+
+/* static */ void
+XPCJSContext::WeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data)
+{
+ // Called immediately after the ZoneGroup weak pointer callback, but only
+ // once for each compartment that is being swept.
+ XPCJSContext* self = static_cast<XPCJSContext*>(data);
+ CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
+ if (xpcComp)
+ xpcComp->UpdateWeakPointersAfterGC(self);
+}
+
+void
+CompartmentPrivate::UpdateWeakPointersAfterGC(XPCJSContext* context)
+{
+ mWrappedJSMap->UpdateWeakPointersAfterGC(context);
+}
+
+static void WatchdogMain(void* arg);
+class Watchdog;
+class WatchdogManager;
+class AutoLockWatchdog {
+ Watchdog* const mWatchdog;
+ public:
+ explicit AutoLockWatchdog(Watchdog* aWatchdog);
+ ~AutoLockWatchdog();
+};
+
+class Watchdog
+{
+ public:
+ explicit Watchdog(WatchdogManager* aManager)
+ : mManager(aManager)
+ , mLock(nullptr)
+ , mWakeup(nullptr)
+ , mThread(nullptr)
+ , mHibernating(false)
+ , mInitialized(false)
+ , mShuttingDown(false)
+ , mMinScriptRunTimeSeconds(1)
+ {}
+ ~Watchdog() { MOZ_ASSERT(!Initialized()); }
+
+ WatchdogManager* Manager() { return mManager; }
+ bool Initialized() { return mInitialized; }
+ bool ShuttingDown() { return mShuttingDown; }
+ PRLock* GetLock() { return mLock; }
+ bool Hibernating() { return mHibernating; }
+ void WakeUp()
+ {
+ MOZ_ASSERT(Initialized());
+ MOZ_ASSERT(Hibernating());
+ mHibernating = false;
+ PR_NotifyCondVar(mWakeup);
+ }
+
+ //
+ // Invoked by the main thread only.
+ //
+
+ void Init()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mLock = PR_NewLock();
+ if (!mLock)
+ NS_RUNTIMEABORT("PR_NewLock failed.");
+ mWakeup = PR_NewCondVar(mLock);
+ if (!mWakeup)
+ NS_RUNTIMEABORT("PR_NewCondVar failed.");
+
+ {
+ AutoLockWatchdog lock(this);
+
+ // Gecko uses thread private for accounting and has to clean up at thread exit.
+ // Therefore, even though we don't have a return value from the watchdog, we need to
+ // join it on shutdown.
+ mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD, 0);
+ if (!mThread)
+ NS_RUNTIMEABORT("PR_CreateThread failed!");
+
+ // WatchdogMain acquires the lock and then asserts mInitialized. So
+ // make sure to set mInitialized before releasing the lock here so
+ // that it's atomic with the creation of the thread.
+ mInitialized = true;
+ }
+ }
+
+ void Shutdown()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(Initialized());
+ { // Scoped lock.
+ AutoLockWatchdog lock(this);
+
+ // Signal to the watchdog thread that it's time to shut down.
+ mShuttingDown = true;
+
+ // Wake up the watchdog, and wait for it to call us back.
+ PR_NotifyCondVar(mWakeup);
+ }
+
+ PR_JoinThread(mThread);
+
+ // The thread sets mShuttingDown to false as it exits.
+ MOZ_ASSERT(!mShuttingDown);
+
+ // Destroy state.
+ mThread = nullptr;
+ PR_DestroyCondVar(mWakeup);
+ mWakeup = nullptr;
+ PR_DestroyLock(mLock);
+ mLock = nullptr;
+
+ // All done.
+ mInitialized = false;
+ }
+
+ void SetMinScriptRunTimeSeconds(int32_t seconds)
+ {
+ // This variable is atomic, and is set from the main thread without
+ // locking.
+ MOZ_ASSERT(seconds > 0);
+ mMinScriptRunTimeSeconds = seconds;
+ }
+
+ //
+ // Invoked by the watchdog thread only.
+ //
+
+ void Hibernate()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mHibernating = true;
+ Sleep(PR_INTERVAL_NO_TIMEOUT);
+ }
+ void Sleep(PRIntervalTime timeout)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS);
+ }
+ void Finished()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mShuttingDown = false;
+ }
+
+ int32_t MinScriptRunTimeSeconds()
+ {
+ return mMinScriptRunTimeSeconds;
+ }
+
+ private:
+ WatchdogManager* mManager;
+
+ PRLock* mLock;
+ PRCondVar* mWakeup;
+ PRThread* mThread;
+ bool mHibernating;
+ bool mInitialized;
+ bool mShuttingDown;
+ mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
+};
+
+#define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
+#define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
+
+class WatchdogManager : public nsIObserver
+{
+ public:
+
+ NS_DECL_ISUPPORTS
+ explicit WatchdogManager(XPCJSContext* aContext) : mContext(aContext)
+ , mContextState(CONTEXT_INACTIVE)
+ {
+ // All the timestamps start at zero except for context state change.
+ PodArrayZero(mTimestamps);
+ mTimestamps[TimestampContextStateChange] = PR_Now();
+
+ // Enable the watchdog, if appropriate.
+ RefreshWatchdog();
+
+ // Register ourselves as an observer to get updates on the pref.
+ mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
+ mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
+ mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+ }
+
+ protected:
+
+ virtual ~WatchdogManager()
+ {
+ // Shutting down the watchdog requires context-switching to the watchdog
+ // thread, which isn't great to do in a destructor. So we require
+ // consumers to shut it down manually before releasing it.
+ MOZ_ASSERT(!mWatchdog);
+ mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog");
+ mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
+ mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
+ }
+
+ public:
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override
+ {
+ RefreshWatchdog();
+ return NS_OK;
+ }
+
+ // Context statistics. These live on the watchdog manager, are written
+ // from the main thread, and are read from the watchdog thread (holding
+ // the lock in each case).
+ void
+ RecordContextActivity(bool active)
+ {
+ // The watchdog reads this state, so acquire the lock before writing it.
+ MOZ_ASSERT(NS_IsMainThread());
+ Maybe<AutoLockWatchdog> lock;
+ if (mWatchdog)
+ lock.emplace(mWatchdog);
+
+ // Write state.
+ mTimestamps[TimestampContextStateChange] = PR_Now();
+ mContextState = active ? CONTEXT_ACTIVE : CONTEXT_INACTIVE;
+
+ // The watchdog may be hibernating, waiting for the context to go
+ // active. Wake it up if necessary.
+ if (active && mWatchdog && mWatchdog->Hibernating())
+ mWatchdog->WakeUp();
+ }
+ bool IsContextActive() { return mContextState == CONTEXT_ACTIVE; }
+ PRTime TimeSinceLastContextStateChange()
+ {
+ return PR_Now() - GetTimestamp(TimestampContextStateChange);
+ }
+
+ // Note - Because of the context activity timestamp, these are read and
+ // written from both threads.
+ void RecordTimestamp(WatchdogTimestampCategory aCategory)
+ {
+ // The watchdog thread always holds the lock when it runs.
+ Maybe<AutoLockWatchdog> maybeLock;
+ if (NS_IsMainThread() && mWatchdog)
+ maybeLock.emplace(mWatchdog);
+ mTimestamps[aCategory] = PR_Now();
+ }
+ PRTime GetTimestamp(WatchdogTimestampCategory aCategory)
+ {
+ // The watchdog thread always holds the lock when it runs.
+ Maybe<AutoLockWatchdog> maybeLock;
+ if (NS_IsMainThread() && mWatchdog)
+ maybeLock.emplace(mWatchdog);
+ return mTimestamps[aCategory];
+ }
+
+ XPCJSContext* Context() { return mContext; }
+ Watchdog* GetWatchdog() { return mWatchdog; }
+
+ void RefreshWatchdog()
+ {
+ bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true);
+ if (wantWatchdog != !!mWatchdog) {
+ if (wantWatchdog)
+ StartWatchdog();
+ else
+ StopWatchdog();
+ }
+
+ if (mWatchdog) {
+ int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
+ if (contentTime <= 0)
+ contentTime = INT32_MAX;
+ int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
+ if (chromeTime <= 0)
+ chromeTime = INT32_MAX;
+ mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime));
+ }
+ }
+
+ void StartWatchdog()
+ {
+ MOZ_ASSERT(!mWatchdog);
+ mWatchdog = new Watchdog(this);
+ mWatchdog->Init();
+ }
+
+ void StopWatchdog()
+ {
+ MOZ_ASSERT(mWatchdog);
+ mWatchdog->Shutdown();
+ mWatchdog = nullptr;
+ }
+
+ private:
+ XPCJSContext* mContext;
+ nsAutoPtr<Watchdog> mWatchdog;
+
+ enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mContextState;
+ PRTime mTimestamps[TimestampCount];
+};
+
+NS_IMPL_ISUPPORTS(WatchdogManager, nsIObserver)
+
+AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog)
+{
+ PR_Lock(mWatchdog->GetLock());
+}
+
+AutoLockWatchdog::~AutoLockWatchdog()
+{
+ PR_Unlock(mWatchdog->GetLock());
+}
+
+static void
+WatchdogMain(void* arg)
+{
+ PR_SetCurrentThreadName("JS Watchdog");
+
+ Watchdog* self = static_cast<Watchdog*>(arg);
+ WatchdogManager* manager = self->Manager();
+
+ // Lock lasts until we return
+ AutoLockWatchdog lock(self);
+
+ MOZ_ASSERT(self->Initialized());
+ MOZ_ASSERT(!self->ShuttingDown());
+ while (!self->ShuttingDown()) {
+ // Sleep only 1 second if recently (or currently) active; otherwise, hibernate
+ if (manager->IsContextActive() ||
+ manager->TimeSinceLastContextStateChange() <= PRTime(2*PR_USEC_PER_SEC))
+ {
+ self->Sleep(PR_TicksPerSecond());
+ } else {
+ manager->RecordTimestamp(TimestampWatchdogHibernateStart);
+ self->Hibernate();
+ manager->RecordTimestamp(TimestampWatchdogHibernateStop);
+ }
+
+ // Rise and shine.
+ manager->RecordTimestamp(TimestampWatchdogWakeup);
+
+ // Don't request an interrupt callback unless the current script has
+ // been running long enough that we might show the slow script dialog.
+ // Triggering the callback from off the main thread can be expensive.
+
+ // We want to avoid showing the slow script dialog if the user's laptop
+ // goes to sleep in the middle of running a script. To ensure this, we
+ // invoke the interrupt callback after only half the timeout has
+ // elapsed. The callback simply records the fact that it was called in
+ // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
+ // seconds and invoke the callback again. This time around it sees
+ // mSlowScriptSecondHalf is set and so it shows the slow script
+ // dialog. If the computer is put to sleep during one of the (timeout/2)
+ // periods, the script still has the other (timeout/2) seconds to
+ // finish.
+ PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2;
+ if (manager->IsContextActive() &&
+ manager->TimeSinceLastContextStateChange() >= usecs)
+ {
+ bool debuggerAttached = false;
+ nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
+ if (dbg)
+ dbg->GetIsDebuggerAttached(&debuggerAttached);
+ if (!debuggerAttached)
+ JS_RequestInterruptCallback(manager->Context()->Context());
+ }
+ }
+
+ // Tell the manager that we've shut down.
+ self->Finished();
+}
+
+PRTime
+XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)
+{
+ return mWatchdogManager->GetTimestamp(aCategory);
+}
+
+void
+xpc::SimulateActivityCallback(bool aActive)
+{
+ XPCJSContext::ActivityCallback(XPCJSContext::Get(), aActive);
+}
+
+// static
+void
+XPCJSContext::ActivityCallback(void* arg, bool active)
+{
+ if (!active) {
+ ProcessHangMonitor::ClearHang();
+ }
+
+ XPCJSContext* self = static_cast<XPCJSContext*>(arg);
+ self->mWatchdogManager->RecordContextActivity(active);
+}
+
+// static
+bool
+XPCJSContext::InterruptCallback(JSContext* cx)
+{
+ XPCJSContext* self = XPCJSContext::Get();
+
+ // Normally we record mSlowScriptCheckpoint when we start to process an
+ // event. However, we can run JS outside of event handlers. This code takes
+ // care of that case.
+ if (self->mSlowScriptCheckpoint.IsNull()) {
+ self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+ self->mSlowScriptSecondHalf = false;
+ self->mSlowScriptActualWait = mozilla::TimeDuration();
+ self->mTimeoutAccumulated = false;
+ return true;
+ }
+
+ // Sometimes we get called back during XPConnect initialization, before Gecko
+ // has finished bootstrapping. Avoid crashing in nsContentUtils below.
+ if (!nsContentUtils::IsInitialized())
+ return true;
+
+ // This is at least the second interrupt callback we've received since
+ // returning to the event loop. See how long it's been, and what the limit
+ // is.
+ TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
+ bool chrome = nsContentUtils::IsCallerChrome();
+ const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
+ : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
+ int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
+
+ // If there's no limit, or we're within the limit, let it go.
+ if (limit == 0 || duration.ToSeconds() < limit / 2.0)
+ return true;
+
+ self->mSlowScriptActualWait += duration;
+
+ // In order to guard against time changes or laptops going to sleep, we
+ // don't trigger the slow script warning until (limit/2) seconds have
+ // elapsed twice.
+ if (!self->mSlowScriptSecondHalf) {
+ self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+ self->mSlowScriptSecondHalf = true;
+ return true;
+ }
+
+ //
+ // This has gone on long enough! Time to take action. ;-)
+ //
+
+ // Get the DOM window associated with the running script. If the script is
+ // running in a non-DOM scope, we have to just let it keep running.
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ RefPtr<nsGlobalWindow> win = WindowOrNull(global);
+ if (!win && IsSandbox(global)) {
+ // If this is a sandbox associated with a DOMWindow via a
+ // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
+ // and JetPack content scripts.
+ JS::Rooted<JSObject*> proto(cx);
+ if (!JS_GetPrototype(cx, global, &proto))
+ return false;
+ if (proto && IsSandboxPrototypeProxy(proto) &&
+ (proto = js::CheckedUnwrap(proto, /* stopAtWindowProxy = */ false)))
+ {
+ win = WindowGlobalOrNull(proto);
+ }
+ }
+
+ if (!win) {
+ NS_WARNING("No active window");
+ return true;
+ }
+
+ if (win->IsDying()) {
+ // The window is being torn down. When that happens we try to prevent
+ // the dispatch of new runnables, so it also makes sense to kill any
+ // long-running script. The user is primarily interested in this page
+ // going away.
+ return false;
+ }
+
+ if (win->GetIsPrerendered()) {
+ // We cannot display a dialog if the page is being prerendered, so
+ // just kill the page.
+ mozilla::dom::HandlePrerenderingViolation(win->AsInner());
+ return false;
+ }
+
+ // Accumulate slow script invokation delay.
+ if (!chrome && !self->mTimeoutAccumulated) {
+ uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0));
+ Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
+ self->mTimeoutAccumulated = true;
+ }
+
+ // Show the prompt to the user, and kill if requested.
+ nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
+ if (response == nsGlobalWindow::KillSlowScript) {
+ if (Preferences::GetBool("dom.global_stop_script", true))
+ xpc::Scriptability::Get(global).Block();
+ return false;
+ }
+
+ // The user chose to continue the script. Reset the timer, and disable this
+ // machinery with a pref of the user opted out of future slow-script dialogs.
+ if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
+ self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
+
+ if (response == nsGlobalWindow::AlwaysContinueSlowScript)
+ Preferences::SetInt(prefName, 0);
+
+ return true;
+}
+
+void
+XPCJSContext::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(NS_LITERAL_STRING("due-to-JS-OOM"),
+ /* anonymize = */ false,
+ /* minimizeMemoryUsage = */ false);
+}
+
+void
+XPCJSContext::CustomLargeAllocationFailureCallback()
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+ }
+}
+
+size_t
+XPCJSContext::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 += CycleCollectedJSContext::SizeOfExcludingThis(mallocSizeOf);
+
+ // There are other XPCJSContext 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;
+}
+
+/***************************************************************************/
+
+#define JS_OPTIONS_DOT_STR "javascript.options."
+
+static void
+ReloadPrefsCallback(const char* pref, void* data)
+{
+ XPCJSContext* xpccx = reinterpret_cast<XPCJSContext*>(data);
+ JSContext* cx = xpccx->Context();
+
+ bool safeMode = false;
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
+ if (xr) {
+ xr->GetInSafeMode(&safeMode);
+ }
+
+ bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode;
+ bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode;
+ bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode;
+ bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm") && !safeMode;
+ bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit") && !safeMode;
+ bool throwOnAsmJSValidationFailure = Preferences::GetBool(JS_OPTIONS_DOT_STR
+ "throw_on_asmjs_validation_failure");
+ bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode;
+
+ bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing");
+ bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR
+ "ion.offthread_compilation");
+ bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR
+ "baselinejit.unsafe_eager_compilation");
+ bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
+
+ sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
+
+ bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack");
+
+ bool throwOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
+ "throw_on_debuggee_would_run");
+
+ bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
+ "dump_stack_on_debuggee_would_run");
+
+ bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
+
+ bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
+
+ sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
+
+#ifdef DEBUG
+ sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug");
+#endif
+
+#ifdef JS_GC_ZEAL
+ int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1);
+ int32_t zeal_frequency =
+ Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal.frequency",
+ JS_DEFAULT_ZEAL_FREQ);
+ if (zeal >= 0) {
+ JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency);
+ }
+#endif // JS_GC_ZEAL
+
+ JS::ContextOptionsRef(cx).setBaseline(useBaseline)
+ .setIon(useIon)
+ .setAsmJS(useAsmJS)
+ .setWasm(useWasm)
+ .setWasmAlwaysBaseline(useWasmBaseline)
+ .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
+ .setNativeRegExp(useNativeRegExp)
+ .setAsyncStack(useAsyncStack)
+ .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
+ .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
+ .setWerror(werror)
+ .setExtraWarnings(extraWarnings);
+
+ JS_SetParallelParsingEnabled(cx, parallelParsing);
+ JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
+ useBaselineEager ? 0 : -1);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
+ useIonEager ? 0 : -1);
+}
+
+XPCJSContext::~XPCJSContext()
+{
+ // Elsewhere we abort immediately if XPCJSContext initialization fails.
+ // Therefore the context must be non-null.
+ MOZ_ASSERT(MaybeContext());
+
+ // 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. Null out the relevant callbacks.
+ js::SetActivityCallback(Context(), nullptr, nullptr);
+ JS_RemoveFinalizeCallback(Context(), FinalizeCallback);
+ JS_RemoveWeakPointerZoneGroupCallback(Context(), WeakPointerZoneGroupCallback);
+ JS_RemoveWeakPointerCompartmentCallback(Context(), WeakPointerCompartmentCallback);
+
+ // Clear any pending exception. It might be an XPCWrappedJS, and if we try
+ // to destroy it later we will crash.
+ SetPendingException(nullptr);
+
+ JS::SetGCSliceCallback(Context(), mPrevGCSliceCallback);
+
+ xpc_DelocalizeContext(Context());
+
+ if (mWatchdogManager->GetWatchdog())
+ mWatchdogManager->StopWatchdog();
+
+ if (mCallContext)
+ mCallContext->SystemIsBeingShutDown();
+
+ auto rtPrivate = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(Context()));
+ delete rtPrivate;
+ JS_SetContextPrivate(Context(), nullptr);
+
+ // clean up and destroy maps...
+ mWrappedJSMap->ShutdownMarker();
+ delete mWrappedJSMap;
+ mWrappedJSMap = nullptr;
+
+ delete mWrappedJSClassMap;
+ mWrappedJSClassMap = nullptr;
+
+ delete mIID2NativeInterfaceMap;
+ mIID2NativeInterfaceMap = nullptr;
+
+ delete mClassInfo2NativeSetMap;
+ mClassInfo2NativeSetMap = nullptr;
+
+ delete mNativeSetMap;
+ mNativeSetMap = nullptr;
+
+ delete mThisTranslatorMap;
+ mThisTranslatorMap = nullptr;
+
+ delete mDyingWrappedNativeProtoMap;
+ mDyingWrappedNativeProtoMap = nullptr;
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ // Tell the profiler that the context is gone
+ if (PseudoStack* stack = mozilla_get_pseudo_stack())
+ stack->sampleContext(nullptr);
+#endif
+
+ Preferences::UnregisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
+}
+
+// If |*anonymizeID| is non-zero and this is a user compartment, the name will
+// be anonymized.
+static void
+GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID,
+ bool replaceSlashes)
+{
+ if (js::IsAtomsCompartment(c)) {
+ name.AssignLiteral("atoms");
+ } else if (*anonymizeID && !js::IsSystemCompartment(c)) {
+ name.AppendPrintf("<anonymized-%d>", *anonymizeID);
+ *anonymizeID += 1;
+ } else if (JSPrincipals* principals = JS_GetCompartmentPrincipals(c)) {
+ nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name);
+ if (NS_FAILED(rv)) {
+ name.AssignLiteral("(unknown)");
+ }
+
+ // If the compartment's location (name) differs from the principal's
+ // script location, append the compartment's location to allow
+ // differentiation of multiple compartments owned by the same principal
+ // (e.g. components owned by the system or null principal).
+ CompartmentPrivate* compartmentPrivate = CompartmentPrivate::Get(c);
+ if (compartmentPrivate) {
+ const nsACString& location = compartmentPrivate->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.ReplaceASCII(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:
+ // inProcessTabChildGlobal?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 =
+ "inProcessTabChildGlobal?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::GetCurrentCompartmentName(JSContext* cx, nsCString& name)
+{
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!global) {
+ name.AssignLiteral("no global");
+ return;
+ }
+
+ JSCompartment* compartment = GetObjectCompartment(global);
+ int anonymizeID = 0;
+ GetCompartmentName(compartment, name, &anonymizeID, false);
+}
+
+void
+xpc::AddGCCallback(xpcGCCallback cb)
+{
+ XPCJSContext::Get()->AddGCCallback(cb);
+}
+
+void
+xpc::RemoveGCCallback(xpcGCCallback cb)
+{
+ XPCJSContext::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 = nsXPConnect::GetContextInstance()->Context();
+ return JS::UserCompartmentCount(cx);
+}
+
+class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter
+{
+ ~JSMainRuntimeTemporaryPeakReporter() {}
+
+ 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 ZCREPORT* macros are for
+// compartments and zones; they aggregate any entries smaller than
+// SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap"
+// entries for the compartment.
+
+#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
+
+#define REPORT(_path, _kind, _units, _amount, _desc) \
+ handleReport->Callback(EmptyCString(), _path, \
+ nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::_units, _amount, \
+ NS_LITERAL_CSTRING(_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(EmptyCString(), _path, \
+ nsIMemoryReporter::KIND_NONHEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ NS_LITERAL_CSTRING(_desc), data); \
+ gcTotal += amount; \
+ } while (0)
+
+// Report compartment/zone non-GC (KIND_HEAP) bytes.
+#define ZCREPORT_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(EmptyCString(), _path, \
+ nsIMemoryReporter::KIND_HEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ NS_LITERAL_CSTRING(_desc), data); \
+ } else { \
+ sundriesMallocHeap += amount; \
+ } \
+ } while (0)
+
+// Report compartment/zone GC bytes.
+#define ZCREPORT_GC_BYTES(_path, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ if (amount >= SUNDRIES_THRESHOLD) { \
+ handleReport->Callback(EmptyCString(), _path, \
+ nsIMemoryReporter::KIND_NONHEAP, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ NS_LITERAL_CSTRING(_desc), data); \
+ gcTotal += amount; \
+ } else { \
+ sundriesGCHeap += amount; \
+ } \
+ } while (0)
+
+// Report runtime bytes.
+#define RREPORT_BYTES(_path, _kind, _amount, _desc) \
+ do { \
+ size_t amount = _amount; /* evaluate _amount only once */ \
+ handleReport->Callback(EmptyCString(), _path, \
+ nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ NS_LITERAL_CSTRING(_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(EmptyCString(), _path, \
+ nsIMemoryReporter::_kind, \
+ nsIMemoryReporter::UNITS_BYTES, amount, \
+ NS_LITERAL_CSTRING(_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, sundriesGCHeap = 0, sundriesMallocHeap = 0;
+
+ MOZ_ASSERT(!gcTotalOut == zStats.isTotals);
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("symbols/gc-heap"),
+ zStats.symbolsGCHeap,
+ "Symbols.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"),
+ zStats.gcHeapArenaAdmin,
+ "Bookkeeping information and alignment padding within GC arenas.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("unused-gc-things"),
+ zStats.unusedGCThings.totalSize(),
+ "Unused GC thing cells within non-empty arenas.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("unique-id-map"),
+ zStats.uniqueIdMap,
+ "Address-independent cell identities.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shape-tables"),
+ zStats.shapeTables,
+ "Tables storing shape information.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/gc-heap"),
+ zStats.lazyScriptsGCHeap,
+ "Scripts that haven't executed yet.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/malloc-heap"),
+ zStats.lazyScriptsMallocHeap,
+ "Lazy script tables containing closed-over bindings or inner functions.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("jit-codes-gc-heap"),
+ zStats.jitCodesGCHeap,
+ "References to executable code pools used by the JITs.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/gc-heap"),
+ zStats.objectGroupsGCHeap,
+ "Classification and type inference information about objects.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/malloc-heap"),
+ zStats.objectGroupsMallocHeap,
+ "Object group addenda.");
+
+ ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/gc-heap"),
+ zStats.scopesGCHeap,
+ "Scope information for scripts.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/malloc-heap"),
+ zStats.scopesMallocHeap,
+ "Arrays of binding names and other binding-related data.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"),
+ zStats.typePool,
+ "Type sets and related data.");
+
+ ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"),
+ zStats.baselineStubsOptimized,
+ "The Baseline JIT's optimized IC stubs (excluding code).");
+
+ 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);
+
+ // 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(NS_LITERAL_CSTRING(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 "%d, copies=%d, \"%s\"%s)/",
+ info.length, info.numCopies, escapedString.get(),
+ truncated ? " (truncated)" : "");
+
+ if (info.gcHeapLatin1 > 0) {
+ REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/latin1"),
+ info.gcHeapLatin1,
+ "Latin1 strings. " MAYBE_INLINE);
+ }
+
+ if (info.gcHeapTwoByte > 0) {
+ REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/two-byte"),
+ info.gcHeapTwoByte,
+ "TwoByte strings. " MAYBE_INLINE);
+ }
+
+ if (info.mallocHeapLatin1 > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/latin1"),
+ KIND_HEAP, info.mallocHeapLatin1,
+ "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ if (info.mallocHeapTwoByte > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/two-byte"),
+ KIND_HEAP, info.mallocHeapTwoByte,
+ "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
+ }
+ }
+
+ nsCString nonNotablePath = pathPrefix;
+ nonNotablePath += (zStats.isTotals || anonymize)
+ ? NS_LITERAL_CSTRING("strings/")
+ : NS_LITERAL_CSTRING("strings/string(<non-notable strings>)/");
+
+ if (zStats.stringInfo.gcHeapLatin1 > 0) {
+ REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/latin1"),
+ zStats.stringInfo.gcHeapLatin1,
+ "Latin1 strings. " MAYBE_INLINE);
+ }
+
+ if (zStats.stringInfo.gcHeapTwoByte > 0) {
+ REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/two-byte"),
+ zStats.stringInfo.gcHeapTwoByte,
+ "TwoByte strings. " MAYBE_INLINE);
+ }
+
+ if (zStats.stringInfo.mallocHeapLatin1 > 0) {
+ REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/latin1"),
+ KIND_HEAP, zStats.stringInfo.mallocHeapLatin1,
+ "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ if (zStats.stringInfo.mallocHeapTwoByte > 0) {
+ REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/two-byte"),
+ KIND_HEAP, zStats.stringInfo.mallocHeapTwoByte,
+ "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED);
+ }
+
+ if (stringsNotableAboutMemoryGCHeap > 0) {
+ MOZ_ASSERT(!zStats.isTotals);
+ REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string(<about-memory>)/gc-heap"),
+ 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 + NS_LITERAL_CSTRING("strings/string(<about-memory>)/malloc-heap"),
+ 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.shapesGCHeapTree > 0) {
+ REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree"),
+ shapeInfo.shapesGCHeapTree,
+ "Shapes in a property tree.");
+ }
+
+ if (shapeInfo.shapesGCHeapDict > 0) {
+ REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/dict"),
+ shapeInfo.shapesGCHeapDict,
+ "Shapes in dictionary mode.");
+ }
+
+ if (shapeInfo.shapesGCHeapBase > 0) {
+ REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/base"),
+ shapeInfo.shapesGCHeapBase,
+ "Base shapes, which collate data common to many shapes.");
+ }
+
+ if (shapeInfo.shapesMallocHeapTreeTables > 0) {
+ REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"),
+ KIND_HEAP, shapeInfo.shapesMallocHeapTreeTables,
+ "Property tables of shapes in a property tree.");
+ }
+
+ if (shapeInfo.shapesMallocHeapDictTables > 0) {
+ REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"),
+ KIND_HEAP, shapeInfo.shapesMallocHeapDictTables,
+ "Property tables of shapes in dictionary mode.");
+ }
+
+ if (shapeInfo.shapesMallocHeapTreeKids > 0) {
+ REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-kids"),
+ KIND_HEAP, shapeInfo.shapesMallocHeapTreeKids,
+ "Kid hashes of shapes in a property tree.");
+ }
+
+ if (sundriesGCHeap > 0) {
+ // We deliberately don't use ZCREPORT_GC_BYTES here.
+ REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"),
+ 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 ZCREPORT_BYTES here.
+ REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"),
+ KIND_HEAP, sundriesMallocHeap,
+ "The sum of all 'malloc-heap' 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 ZCREPORT_BYTES, so that these per-class values
+ // don't go into sundries.
+
+ if (classInfo.objectsGCHeap > 0) {
+ REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("objects/gc-heap"),
+ classInfo.objectsGCHeap,
+ "Objects, including fixed slots.");
+ }
+
+ if (classInfo.objectsMallocHeapSlots > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/slots"),
+ KIND_HEAP, classInfo.objectsMallocHeapSlots,
+ "Non-fixed object slots.");
+ }
+
+ if (classInfo.objectsMallocHeapElementsNormal > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/normal"),
+ KIND_HEAP, classInfo.objectsMallocHeapElementsNormal,
+ "Normal (non-wasm) indexed elements.");
+ }
+
+ if (classInfo.objectsMallocHeapElementsAsmJS > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"),
+ KIND_HEAP, classInfo.objectsMallocHeapElementsAsmJS,
+ "asm.js array buffer elements allocated in the malloc heap.");
+ }
+
+ if (classInfo.objectsMallocHeapMisc > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/misc"),
+ KIND_HEAP, classInfo.objectsMallocHeapMisc,
+ "Miscellaneous object data.");
+ }
+
+ if (classInfo.objectsNonHeapElementsNormal > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/normal"),
+ KIND_NONHEAP, classInfo.objectsNonHeapElementsNormal,
+ "Memory-mapped non-shared array buffer elements.");
+ }
+
+ if (classInfo.objectsNonHeapElementsShared > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/shared"),
+ 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 + NS_LITERAL_CSTRING("objects/non-heap/elements/wasm"),
+ KIND_NONHEAP, classInfo.objectsNonHeapElementsWasm,
+ "wasm/asm.js array buffer elements allocated outside both the "
+ "malloc heap and the GC heap.");
+ }
+
+ if (classInfo.objectsNonHeapCodeWasm > 0) {
+ REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/code/wasm"),
+ KIND_NONHEAP, classInfo.objectsNonHeapCodeWasm,
+ "AOT-compiled wasm/asm.js code.");
+ }
+
+ // 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 (classInfo.wasmGuardPages > 0) {
+ REPORT_BYTES(NS_LITERAL_CSTRING("wasm-guard-pages"),
+ KIND_OTHER, classInfo.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.");
+ }
+}
+
+static void
+ReportCompartmentStats(const JS::CompartmentStats& cStats,
+ const xpc::CompartmentStatsExtras& extras,
+ amIAddonManager* addonManager,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data, size_t* gcTotalOut = nullptr)
+{
+ static const nsDependentCString addonPrefix("explicit/add-ons/");
+
+ size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0;
+ nsAutoCString cJSPathPrefix(extras.jsPathPrefix);
+ nsAutoCString cDOMPathPrefix(extras.domPathPrefix);
+
+ MOZ_ASSERT(!gcTotalOut == cStats.isTotals);
+
+ // Only attempt to prefix if we got a location and the path wasn't already
+ // prefixed.
+ if (extras.location && addonManager &&
+ cJSPathPrefix.Find(addonPrefix, false, 0, 0) != 0) {
+ nsAutoCString addonId;
+ bool ok;
+ if (NS_SUCCEEDED(addonManager->MapURIToAddonID(extras.location,
+ addonId, &ok))
+ && ok) {
+ // Insert the add-on id as "add-ons/@id@/" after "explicit/" to
+ // aggregate add-on compartments.
+ static const size_t explicitLength = strlen("explicit/");
+ addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
+ addonId += "/";
+ cJSPathPrefix.Insert(addonId, explicitLength);
+ cDOMPathPrefix.Insert(addonId, explicitLength);
+ }
+ }
+
+ nsCString nonNotablePath = cJSPathPrefix;
+ nonNotablePath += cStats.isTotals
+ ? NS_LITERAL_CSTRING("classes/")
+ : NS_LITERAL_CSTRING("classes/class(<non-notable classes>)/");
+
+ ReportClassStats(cStats.classInfo, nonNotablePath, handleReport, data,
+ gcTotal);
+
+ for (size_t i = 0; i < cStats.notableClasses.length(); i++) {
+ MOZ_ASSERT(!cStats.isTotals);
+ const JS::NotableClassInfo& classInfo = cStats.notableClasses[i];
+
+ nsCString classPath = cJSPathPrefix +
+ nsPrintfCString("classes/class(%s)/", classInfo.className_);
+
+ ReportClassStats(classInfo, classPath, handleReport, data, gcTotal);
+ }
+
+ // Note that we use cDOMPathPrefix 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.
+ ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
+ cStats.objectsPrivate,
+ "Orphan DOM nodes, i.e. those that are only reachable from JavaScript "
+ "objects.");
+
+ ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"),
+ cStats.scriptsGCHeap,
+ "JSScript instances. There is one per user-defined function in a "
+ "script, and one for the top-level code in a script.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/malloc-heap/data"),
+ cStats.scriptsMallocHeapData,
+ "Various variable-length tables in JSScripts.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/data"),
+ cStats.baselineData,
+ "The Baseline JIT's compilation data (BaselineScripts).");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/fallback-stubs"),
+ cStats.baselineStubsFallback,
+ "The Baseline JIT's fallback IC stubs (excluding code).");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("ion-data"),
+ cStats.ionData,
+ "The IonMonkey JIT's compilation data (IonScripts).");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/type-scripts"),
+ cStats.typeInferenceTypeScripts,
+ "Type sets associated with scripts.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/allocation-site-tables"),
+ cStats.typeInferenceAllocationSiteTables,
+ "Tables of type objects associated with allocation sites.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/array-type-tables"),
+ cStats.typeInferenceArrayTypeTables,
+ "Tables of type objects associated with array literals.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/object-type-tables"),
+ cStats.typeInferenceObjectTypeTables,
+ "Tables of type objects associated with object literals.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"),
+ cStats.compartmentObject,
+ "The JSCompartment object itself.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"),
+ cStats.compartmentTables,
+ "Compartment-wide tables storing object group information and wasm instances.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"),
+ cStats.innerViewsTable,
+ "The table for array buffer inner views.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"),
+ cStats.lazyArrayBuffersTable,
+ "The table for typed object lazy array buffers.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("object-metadata"),
+ cStats.objectMetadataTable,
+ "The table used by debugging tools for tracking object metadata");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"),
+ cStats.crossCompartmentWrappersTable,
+ "The cross-compartment wrapper table.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"),
+ cStats.regexpCompartment,
+ "The regexp compartment and regexp data.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("saved-stacks-set"),
+ cStats.savedStacksSet,
+ "The saved stacks set.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("non-syntactic-lexical-scopes-table"),
+ cStats.nonSyntacticLexicalScopesTable,
+ "The non-syntactic lexical scopes table.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("jit-compartment"),
+ cStats.jitCompartment,
+ "The JIT compartment.");
+
+ ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("private-data"),
+ cStats.privateData,
+ "Extra data attached to the compartment by XPConnect, including "
+ "its wrapped-js.");
+
+ if (sundriesGCHeap > 0) {
+ // We deliberately don't use ZCREPORT_GC_BYTES here.
+ REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"),
+ 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 ZCREPORT_BYTES here.
+ REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"),
+ 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 + NS_LITERAL_CSTRING("misc"),
+ KIND_HEAP, scriptSourceInfo.misc,
+ "Miscellaneous data relating to JavaScript source code.");
+ }
+}
+
+static void
+ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats,
+ const nsACString& rtPath,
+ amIAddonManager* addonManager,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data,
+ bool anonymize,
+ size_t* rtTotalOut)
+{
+ size_t gcTotal = 0;
+
+ for (size_t i = 0; i < rtStats.zoneStatsVector.length(); i++) {
+ const JS::ZoneStats& zStats = rtStats.zoneStatsVector[i];
+ const xpc::ZoneStatsExtras* extras =
+ static_cast<const xpc::ZoneStatsExtras*>(zStats.extra);
+ ReportZoneStats(zStats, *extras, handleReport, data, anonymize,
+ &gcTotal);
+ }
+
+ for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
+ const JS::CompartmentStats& cStats = rtStats.compartmentStatsVector[i];
+ const xpc::CompartmentStatsExtras* extras =
+ static_cast<const xpc::CompartmentStatsExtras*>(cStats.extra);
+
+ ReportCompartmentStats(cStats, *extras, addonManager, handleReport,
+ data, &gcTotal);
+ }
+
+ // Report the rtStats.runtime numbers under "runtime/", and compute their
+ // total for later.
+
+ size_t rtTotal = 0;
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/runtime-object"),
+ KIND_HEAP, rtStats.runtime.object,
+ "The JSRuntime object.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/atoms-table"),
+ KIND_HEAP, rtStats.runtime.atomsTable,
+ "The atoms table.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/contexts"),
+ KIND_HEAP, rtStats.runtime.contexts,
+ "JSContext objects and structures that belong to them.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/temporary"),
+ KIND_HEAP, rtStats.runtime.temporary,
+ "Transient data (mostly parse nodes) held by the JSRuntime during "
+ "compilation.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"),
+ KIND_HEAP, rtStats.runtime.interpreterStack,
+ "JS interpreter frames.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
+ KIND_HEAP, rtStats.runtime.mathCache,
+ "The math cache.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-immutable-strings-cache"),
+ KIND_HEAP, rtStats.runtime.sharedImmutableStringsCache,
+ "Immutable strings (such as JS scripts' source text) shared across all JSRuntimes.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-intl-data"),
+ KIND_HEAP, rtStats.runtime.sharedIntlData,
+ "Shared internationalization data.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/uncompressed-source-cache"),
+ KIND_HEAP, rtStats.runtime.uncompressedSourceCache,
+ "The uncompressed source code cache.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"),
+ 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_);
+ 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 + NS_LITERAL_CSTRING("runtime/code/ion"),
+ KIND_NONHEAP, rtStats.runtime.code.ion,
+ "Code generated by the IonMonkey JIT.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/baseline"),
+ KIND_NONHEAP, rtStats.runtime.code.baseline,
+ "Code generated by the Baseline JIT.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/regexp"),
+ KIND_NONHEAP, rtStats.runtime.code.regexp,
+ "Code generated by the regexp JIT.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/other"),
+ KIND_NONHEAP, rtStats.runtime.code.other,
+ "Code generated by the JITs for wrappers and trampolines.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/unused"),
+ KIND_NONHEAP, rtStats.runtime.code.unused,
+ "Memory allocated by one of the JITs to hold code, but which is "
+ "currently unused.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"),
+ KIND_HEAP, rtStats.runtime.gc.marker,
+ "The GC mark stack and gray roots.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"),
+ KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted,
+ "Memory being used by the GC's nursery.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-malloced-buffers"),
+ KIND_HEAP, rtStats.runtime.gc.nurseryMallocedBuffers,
+ "Out-of-line slots and elements belonging to objects in the nursery.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"),
+ KIND_HEAP, rtStats.runtime.gc.storeBufferVals,
+ "Values in the store buffer.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"),
+ KIND_HEAP, rtStats.runtime.gc.storeBufferCells,
+ "Cells in the store buffer.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/slots"),
+ KIND_HEAP, rtStats.runtime.gc.storeBufferSlots,
+ "Slots in the store buffer.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/whole-cells"),
+ KIND_HEAP, rtStats.runtime.gc.storeBufferWholeCells,
+ "Whole cells in the store buffer.");
+
+ RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/generics"),
+ KIND_HEAP, rtStats.runtime.gc.storeBufferGenerics,
+ "Generic things in the store buffer.");
+
+ if (rtTotalOut)
+ *rtTotalOut = rtTotal;
+
+ // Report GC numbers that don't belong to a compartment.
+
+ // We don't want to report decommitted memory in "explicit", so we just
+ // change the leading "explicit/" to "decommitted/".
+ nsCString rtPath2(rtPath);
+ rtPath2.Replace(0, strlen("explicit"), NS_LITERAL_CSTRING("decommitted"));
+ REPORT_GC_BYTES(rtPath2 + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"),
+ rtStats.gcHeapDecommittedArenas,
+ "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 + NS_LITERAL_CSTRING("gc-heap/unused-chunks"),
+ rtStats.gcHeapUnusedChunks,
+ "Empty GC chunks which will soon be released unless claimed for new "
+ "allocations.");
+
+ REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-arenas"),
+ rtStats.gcHeapUnusedArenas,
+ "Empty GC arenas within non-empty chunks.");
+
+ REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/chunk-admin"),
+ 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);
+}
+
+void
+ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats,
+ const nsACString& rtPath,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data,
+ bool anonymize,
+ size_t* rtTotalOut)
+{
+ nsCOMPtr<amIAddonManager> am;
+ if (XRE_IsParentProcess()) {
+ // Only try to access the service from the main process.
+ am = do_GetService("@mozilla.org/addons/integration;1");
+ }
+ ReportJSRuntimeExplicitTreeStats(rtStats, rtPath, am.get(), handleReport,
+ data, anonymize, rtTotalOut);
+}
+
+
+} // namespace xpc
+
+class JSMainRuntimeCompartmentsReporter final : public nsIMemoryReporter
+{
+
+ ~JSMainRuntimeCompartmentsReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ struct Data {
+ int anonymizeID;
+ js::Vector<nsCString, 0, js::SystemAllocPolicy> paths;
+ };
+
+ static void CompartmentCallback(JSContext* cx, void* vdata, JSCompartment* c) {
+ // silently ignore OOM errors
+ Data* data = static_cast<Data*>(vdata);
+ nsCString path;
+ GetCompartmentName(c, path, &data->anonymizeID, /* replaceSlashes = */ true);
+ path.Insert(js::IsSystemCompartment(c)
+ ? NS_LITERAL_CSTRING("js-main-runtime-compartments/system/")
+ : NS_LITERAL_CSTRING("js-main-runtime-compartments/user/"),
+ 0);
+ mozilla::Unused << data->paths.append(path);
+ }
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport,
+ nsISupports* data, bool anonymize) override
+ {
+ // First we collect the compartment paths. Then we report them. Doing
+ // the two steps interleaved is a bad idea, because calling
+ // |handleReport| from within CompartmentCallback() leads to all manner
+ // of assertions.
+
+ Data d;
+ d.anonymizeID = anonymize ? 1 : 0;
+ JS_IterateCompartments(nsXPConnect::GetContextInstance()->Context(),
+ &d, CompartmentCallback);
+
+ for (size_t i = 0; i < d.paths.length(); i++)
+ REPORT(nsCString(d.paths[i]), KIND_OTHER, UNITS_COUNT, 1,
+ "A live compartment in the main JSRuntime.");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(JSMainRuntimeCompartmentsReporter, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf)
+
+namespace xpc {
+
+static size_t
+SizeOfTreeIncludingThis(nsINode* tree)
+{
+ size_t n = tree->SizeOfIncludingThis(OrphanMallocSizeOf);
+ for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree))
+ n += child->SizeOfIncludingThis(OrphanMallocSizeOf);
+
+ return n;
+}
+
+class OrphanReporter : public JS::ObjectPrivateVisitor
+{
+ public:
+ explicit OrphanReporter(GetISupportsFun aGetISupports)
+ : JS::ObjectPrivateVisitor(aGetISupports)
+ {
+ }
+
+ virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override {
+ size_t n = 0;
+ nsCOMPtr<nsINode> node = do_QueryInterface(aSupports);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains
+ // that we have to skip XBL elements because they violate certain
+ // assumptions. Yuk.
+ if (node && !node->IsInUncomposedDoc() &&
+ !(node->IsElement() && node->AsElement()->IsInNamespace(kNameSpaceID_XBL)))
+ {
+ // 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 &&
+ !mAlreadyMeasuredOrphanTrees.Contains(orphanTree)) {
+ // If PutEntry() fails we don't measure this tree, which could
+ // lead to under-measurement. But that's better than the
+ // alternatives, which are over-measurement or an OOM abort.
+ if (mAlreadyMeasuredOrphanTrees.PutEntry(orphanTree, fallible)) {
+ n += SizeOfTreeIncludingThis(orphanTree);
+ }
+ }
+ }
+ return n;
+ }
+
+ private:
+ nsTHashtable <nsISupportsHashKey> mAlreadyMeasuredOrphanTrees;
+};
+
+#ifdef DEBUG
+static bool
+StartsWithExplicit(nsACString& s)
+{
+ return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/"));
+}
+#endif
+
+class XPCJSContextStats : public JS::RuntimeStats
+{
+ WindowPaths* mWindowPaths;
+ WindowPaths* mTopWindowPaths;
+ bool mGetLocations;
+ int mAnonymizeID;
+
+ public:
+ XPCJSContextStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths,
+ bool getLocations, bool anonymize)
+ : JS::RuntimeStats(JSMallocSizeOf),
+ mWindowPaths(windowPaths),
+ mTopWindowPaths(topWindowPaths),
+ mGetLocations(getLocations),
+ mAnonymizeID(anonymize ? 1 : 0)
+ {}
+
+ ~XPCJSContextStats() {
+ for (size_t i = 0; i != compartmentStatsVector.length(); ++i)
+ delete static_cast<xpc::CompartmentStatsExtras*>(compartmentStatsVector[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) override {
+ // Get the compartment's global.
+ AutoSafeJSContext cx;
+ JSCompartment* comp = js::GetAnyCompartmentInZone(zone);
+ xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
+ extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/");
+ RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, comp));
+ if (global) {
+ RefPtr<nsGlobalWindow> window;
+ if (NS_SUCCEEDED(UNWRAP_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 initExtraCompartmentStats(JSCompartment* c,
+ JS::CompartmentStats* cstats) override
+ {
+ xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras;
+ nsCString cName;
+ GetCompartmentName(c, cName, &mAnonymizeID, /* replaceSlashes = */ true);
+ CompartmentPrivate* cp = CompartmentPrivate::Get(c);
+ if (cp) {
+ if (mGetLocations) {
+ cp->GetLocationURI(CompartmentPrivate::LocationHintAddon,
+ getter_AddRefs(extras->location));
+ }
+ // Note: cannot use amIAddonManager implementation at this point,
+ // as it is a JS service and the JS heap is currently not idle.
+ // Otherwise, we could have computed the add-on id at this point.
+ }
+
+ // Get the compartment's global.
+ AutoSafeJSContext cx;
+ bool needZone = true;
+ RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, c));
+ if (global) {
+ RefPtr<nsGlobalWindow> window;
+ if (NS_SUCCEEDED(UNWRAP_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::GetCompartmentZone(c));
+
+ extras->jsPathPrefix += NS_LITERAL_CSTRING("compartment(") + cName + NS_LITERAL_CSTRING(")/");
+
+ // extras->jsPathPrefix is used for almost all the compartment-specific
+ // reports. At this point it has the form
+ // "<something>compartment(<cname>)/".
+ //
+ // 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 compartment belongs to an nsGlobalWindow, and
+ // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't
+ // be used, because non-nsGlobalWindow compartments shouldn't have
+ // orphan DOM nodes).
+
+ MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
+ MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));
+
+ cstats->extra = extras;
+ }
+};
+
+void
+JSReporter::CollectReports(WindowPaths* windowPaths,
+ WindowPaths* topWindowPaths,
+ nsIHandleReportCallback* handleReport,
+ nsISupports* data,
+ bool anonymize)
+{
+ XPCJSContext* xpccx = nsXPConnect::GetContextInstance();
+
+ // 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.
+
+ nsCOMPtr<amIAddonManager> addonManager;
+ if (XRE_IsParentProcess()) {
+ // Only try to access the service from the main process.
+ addonManager = do_GetService("@mozilla.org/addons/integration;1");
+ }
+ bool getLocations = !!addonManager;
+ XPCJSContextStats rtStats(windowPaths, topWindowPaths, getLocations,
+ anonymize);
+ OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
+ if (!JS::CollectRuntimeStats(xpccx->Context(), &rtStats, &orphanReporter,
+ anonymize))
+ {
+ return;
+ }
+
+ size_t xpcJSRuntimeSize = xpccx->SizeOfIncludingThis(JSMallocSizeOf);
+
+ size_t wrappedJSSize = xpccx->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
+
+ XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf);
+ XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(&sizeInfo);
+
+ mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+ size_t jsComponentLoaderSize = loader ? loader->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,
+ NS_LITERAL_CSTRING("explicit/js-non-window/"),
+ addonManager, handleReport, data,
+ anonymize, &rtTotal);
+
+ // Report the sums of the compartment numbers.
+ xpc::CompartmentStatsExtras cExtrasTotal;
+ cExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/compartments/");
+ cExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/");
+ ReportCompartmentStats(rtStats.cTotals, cExtrasTotal, addonManager,
+ 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(NS_LITERAL_CSTRING("js-main-runtime/runtime"),
+ KIND_OTHER, rtTotal,
+ "The sum of all measurements under 'explicit/js-non-window/runtime/'.");
+
+ // Report the numbers for memory outside of compartments.
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"),
+ KIND_OTHER, rtStats.gcHeapUnusedChunks,
+ "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"),
+ KIND_OTHER, rtStats.gcHeapUnusedArenas,
+ "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/chunk-admin"),
+ 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(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/chunks"),
+ KIND_OTHER, rtStats.gcHeapUnusedChunks,
+ "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/arenas"),
+ KIND_OTHER, rtStats.gcHeapUnusedArenas,
+ "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/object-groups"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.objectGroup,
+ "Unused object group cells within non-empty arenas.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/lazy-scripts"),
+ KIND_OTHER, rtStats.zTotals.unusedGCThings.lazyScript,
+ "Unused lazy script cells within non-empty arenas.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"),
+ KIND_OTHER, rtStats.gcHeapChunkAdmin,
+ "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"),
+ KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin,
+ "The same as 'js-main-runtime/zones/gc-heap-arena-admin'.");
+
+ size_t gcThingTotal = 0;
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/objects"),
+ KIND_OTHER, rtStats.cTotals.classInfo.objectsGCHeap,
+ "Used object cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/strings"),
+ KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(),
+ "Used string cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/symbols"),
+ KIND_OTHER, rtStats.zTotals.symbolsGCHeap,
+ "Used symbol cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/shapes"),
+ KIND_OTHER,
+ rtStats.zTotals.shapeInfo.shapesGCHeapTree + rtStats.zTotals.shapeInfo.shapesGCHeapDict,
+ "Used shape cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"),
+ KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase,
+ "Used base shape cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/object-groups"),
+ KIND_OTHER, rtStats.zTotals.objectGroupsGCHeap,
+ "Used object group cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/scopes"),
+ KIND_OTHER, rtStats.zTotals.scopesGCHeap,
+ "Used scope cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/scripts"),
+ KIND_OTHER, rtStats.cTotals.scriptsGCHeap,
+ "Used script cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/lazy-scripts"),
+ KIND_OTHER, rtStats.zTotals.lazyScriptsGCHeap,
+ "Used lazy script cells.");
+
+ MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/jitcode"),
+ KIND_OTHER, rtStats.zTotals.jitCodesGCHeap,
+ "Used jitcode cells.");
+
+ MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings);
+
+ // Report xpconnect.
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/runtime"),
+ KIND_HEAP, xpcJSRuntimeSize,
+ "The XPConnect runtime.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/wrappedjs"),
+ KIND_HEAP, wrappedJSSize,
+ "Wrappers used to implement XPIDL interfaces with JS.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/scopes"),
+ KIND_HEAP, sizeInfo.mScopeAndMapSize,
+ "XPConnect scopes.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/proto-iface-cache"),
+ KIND_HEAP, sizeInfo.mProtoAndIfaceCacheSize,
+ "Prototype and interface binding caches.");
+
+ REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/js-component-loader"),
+ KIND_HEAP, jsComponentLoaderSize,
+ "XPConnect's JS component loader.");
+}
+
+static nsresult
+JSSizeOfTab(JSObject* objArg, size_t* jsObjectsSize, size_t* jsStringsSize,
+ size_t* jsPrivateSize, size_t* jsOtherSize)
+{
+ JSContext* cx = nsXPConnect::GetContextInstance()->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(int id, uint32_t sample, const char* key)
+{
+ switch (id) {
+ case JS_TELEMETRY_GC_REASON:
+ Telemetry::Accumulate(Telemetry::GC_REASON_2, sample);
+ break;
+ case JS_TELEMETRY_GC_IS_ZONE_GC:
+ Telemetry::Accumulate(Telemetry::GC_IS_COMPARTMENTAL, sample);
+ break;
+ case JS_TELEMETRY_GC_MS:
+ Telemetry::Accumulate(Telemetry::GC_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_BUDGET_MS:
+ Telemetry::Accumulate(Telemetry::GC_BUDGET_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_ANIMATION_MS:
+ Telemetry::Accumulate(Telemetry::GC_ANIMATION_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_MAX_PAUSE_MS:
+ Telemetry::Accumulate(Telemetry::GC_MAX_PAUSE_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_MARK_MS:
+ Telemetry::Accumulate(Telemetry::GC_MARK_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_SWEEP_MS:
+ Telemetry::Accumulate(Telemetry::GC_SWEEP_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_COMPACT_MS:
+ Telemetry::Accumulate(Telemetry::GC_COMPACT_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_MARK_ROOTS_MS:
+ Telemetry::Accumulate(Telemetry::GC_MARK_ROOTS_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_MARK_GRAY_MS:
+ Telemetry::Accumulate(Telemetry::GC_MARK_GRAY_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_SLICE_MS:
+ Telemetry::Accumulate(Telemetry::GC_SLICE_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_SLOW_PHASE:
+ Telemetry::Accumulate(Telemetry::GC_SLOW_PHASE, sample);
+ break;
+ case JS_TELEMETRY_GC_MMU_50:
+ Telemetry::Accumulate(Telemetry::GC_MMU_50, sample);
+ break;
+ case JS_TELEMETRY_GC_RESET:
+ Telemetry::Accumulate(Telemetry::GC_RESET, sample);
+ break;
+ case JS_TELEMETRY_GC_RESET_REASON:
+ Telemetry::Accumulate(Telemetry::GC_RESET_REASON, sample);
+ break;
+ case JS_TELEMETRY_GC_INCREMENTAL_DISABLED:
+ Telemetry::Accumulate(Telemetry::GC_INCREMENTAL_DISABLED, sample);
+ break;
+ case JS_TELEMETRY_GC_NON_INCREMENTAL:
+ Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample);
+ break;
+ case JS_TELEMETRY_GC_NON_INCREMENTAL_REASON:
+ Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL_REASON, sample);
+ break;
+ case JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS:
+ Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_TOTAL_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS:
+ Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_MAX_PAUSE_MS, sample);
+ break;
+ case JS_TELEMETRY_GC_MINOR_REASON:
+ Telemetry::Accumulate(Telemetry::GC_MINOR_REASON, sample);
+ break;
+ case JS_TELEMETRY_GC_MINOR_REASON_LONG:
+ Telemetry::Accumulate(Telemetry::GC_MINOR_REASON_LONG, sample);
+ break;
+ case JS_TELEMETRY_GC_MINOR_US:
+ Telemetry::Accumulate(Telemetry::GC_MINOR_US, sample);
+ break;
+ case JS_TELEMETRY_GC_NURSERY_BYTES:
+ Telemetry::Accumulate(Telemetry::GC_NURSERY_BYTES, sample);
+ break;
+ case JS_TELEMETRY_GC_PRETENURE_COUNT:
+ Telemetry::Accumulate(Telemetry::GC_PRETENURE_COUNT, sample);
+ break;
+ case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT:
+ Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, sample);
+ break;
+ case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS:
+ Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS, sample);
+ break;
+ case JS_TELEMETRY_ADDON_EXCEPTIONS:
+ Telemetry::Accumulate(Telemetry::JS_TELEMETRY_ADDON_EXCEPTIONS, nsDependentCString(key), sample);
+ break;
+ case JS_TELEMETRY_AOT_USAGE:
+ Telemetry::Accumulate(Telemetry::JS_AOT_USAGE, sample);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected JS_TELEMETRY id");
+ }
+}
+
+static void
+CompartmentNameCallback(JSContext* cx, JSCompartment* comp,
+ char* buf, size_t bufsize)
+{
+ nsCString name;
+ // This is called via the JSAPI and isn't involved in memory reporting, so
+ // we don't need to anonymize compartment names.
+ int anonymizeID = 0;
+ GetCompartmentName(comp, name, &anonymizeID, /* replaceSlashes = */ false);
+ if (name.Length() >= bufsize)
+ name.Truncate(bufsize - 1);
+ memcpy(buf, name.get(), name.Length() + 1);
+}
+
+static bool
+PreserveWrapper(JSContext* cx, JSObject* obj)
+{
+ MOZ_ASSERT(cx);
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(IS_WN_REFLECTOR(obj) || mozilla::dom::IsDOMObject(obj));
+
+ return mozilla::dom::IsDOMObject(obj) && mozilla::dom::TryPreserveWrapper(obj);
+}
+
+static nsresult
+ReadSourceFromFilename(JSContext* cx, const char* filename, char16_t** src, size_t* len)
+{
+ 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_DATA_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(NS_LITERAL_CSTRING("text/plain"));
+
+ nsCOMPtr<nsIInputStream> scriptStream;
+ rv = scriptChannel->Open2(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 an internal buf the size of the file.
+ auto buf = MakeUniqueFallible<unsigned char[]>(rawLen);
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ unsigned char* ptr = buf.get();
+ unsigned char* end = ptr + rawLen;
+ while (ptr < end) {
+ uint32_t bytesRead;
+ rv = scriptStream->Read(reinterpret_cast<char*>(ptr), end - ptr, &bytesRead);
+ if (NS_FAILED(rv))
+ return rv;
+ MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF");
+ ptr += bytesRead;
+ }
+
+ rv = nsScriptLoader::ConvertToUTF16(scriptChannel, buf.get(), rawLen, EmptyString(),
+ nullptr, *src, *len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!*src)
+ return NS_ERROR_FAILURE;
+
+ // Historically this method used JS_malloc() which updates the GC memory
+ // accounting. Since ConvertToUTF16() now uses js_malloc() instead we
+ // update the accounting manually after the fact.
+ JS_updateMallocCounter(cx, *len);
+
+ 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 XPCJSContext
+// constructor.
+class XPCJSSourceHook: public js::SourceHook {
+ bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) {
+ *src = nullptr;
+ *length = 0;
+
+ if (!nsContentUtils::IsCallerChrome())
+ return true;
+
+ if (!filename)
+ return true;
+
+ nsresult rv = ReadSourceFromFilename(cx, filename, src, length);
+ if (NS_FAILED(rv)) {
+ xpc::Throw(cx, rv);
+ return false;
+ }
+
+ return true;
+ }
+};
+
+static const JSWrapObjectCallbacks WrapObjectCallbacks = {
+ xpc::WrapperFactory::Rewrap,
+ xpc::WrapperFactory::PrepareForWrapping
+};
+
+XPCJSContext::XPCJSContext()
+ : mCallContext(nullptr),
+ mAutoRoots(nullptr),
+ mResolveName(JSID_VOID),
+ mResolvingWrapper(nullptr),
+ mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)),
+ mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_LENGTH)),
+ mIID2NativeInterfaceMap(IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_LENGTH)),
+ mClassInfo2NativeSetMap(ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
+ mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
+ mThisTranslatorMap(IID2ThisTranslatorMap::newMap(XPC_THIS_TRANSLATOR_MAP_LENGTH)),
+ mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)),
+ mGCIsRunning(false),
+ mNativesToReleaseArray(),
+ mDoingFinalization(false),
+ mVariantRoots(nullptr),
+ mWrappedJSRoots(nullptr),
+ mObjectHolderRoots(nullptr),
+ mWatchdogManager(new WatchdogManager(this)),
+ mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()),
+ mSlowScriptSecondHalf(false),
+ mTimeoutAccumulated(false),
+ mPendingResult(NS_OK)
+{
+}
+
+#ifdef XP_WIN
+static size_t
+GetWindowsStackSize()
+{
+ // First, get the stack base. Because the stack grows down, this is the top
+ // of the stack.
+ const uint8_t* stackTop;
+#ifdef _WIN64
+ PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
+ stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
+#else
+ PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
+ stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
+#endif
+
+ // Now determine the stack bottom. Note that we can't use tib->StackLimit,
+ // because that's the size of the committed area and we're also interested
+ // in the reserved pages below that.
+ MEMORY_BASIC_INFORMATION mbi;
+ if (!VirtualQuery(&mbi, &mbi, sizeof(mbi)))
+ MOZ_CRASH("VirtualQuery failed");
+
+ const uint8_t* stackBottom = reinterpret_cast<const uint8_t*>(mbi.AllocationBase);
+
+ // Do some sanity checks.
+ size_t stackSize = size_t(stackTop - stackBottom);
+ MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024);
+ MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024);
+
+ // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like
+ // the guard page and large PGO stack frames.
+ return stackSize - 10 * sizeof(uintptr_t) * 1024;
+}
+#endif
+
+nsresult
+XPCJSContext::Initialize()
+{
+ nsresult rv = CycleCollectedJSContext::Initialize(nullptr,
+ JS::DefaultHeapMaxBytes,
+ JS::DefaultNurseryBytes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(Context());
+ JSContext* cx = Context();
+
+ mUnprivilegedJunkScope.init(cx, nullptr);
+ mPrivilegedJunkScope.init(cx, nullptr);
+ mCompilationScope.init(cx, nullptr);
+
+ // these jsids filled in later when we have a JSContext to work with.
+ mStrIDs[0] = JSID_VOID;
+
+ auto cxPrivate = new PerThreadAtomCache();
+ memset(cxPrivate, 0, sizeof(PerThreadAtomCache));
+ JS_SetContextPrivate(cx, cxPrivate);
+
+ // Unconstrain the runtime's threshold on nominal heap size, to avoid
+ // triggering GC too often if operating continuously near an arbitrary
+ // finite threshold (0xffffffff is infinity for uint32_t parameters).
+ // This leaves the maximum-JS_malloc-bytes threshold still in effect
+ // to cause period, and we hope hygienic, last-ditch GCs from within
+ // the GC's allocator.
+ JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
+
+ // The JS engine permits us to set different stack limits for system code,
+ // trusted script, and untrusted script. We have tests that ensure that
+ // we can always execute 10 "heavy" (eval+with) stack frames deeper in
+ // privileged code. Our stack sizes vary greatly in different configurations,
+ // so satisfying those tests requires some care. Manual measurements of the
+ // number of heavy stack frames achievable gives us the following rough data,
+ // ordered by the effective categories in which they are grouped in the
+ // JS_SetNativeStackQuota call (which predates this analysis).
+ //
+ // (NB: These numbers may have drifted recently - see bug 938429)
+ // OSX 64-bit Debug: 7MB stack, 636 stack frames => ~11.3k per stack frame
+ // OSX64 Opt: 7MB stack, 2440 stack frames => ~3k per stack frame
+ //
+ // Linux 32-bit Debug: 2MB stack, 426 stack frames => ~4.8k per stack frame
+ // Linux 64-bit Debug: 4MB stack, 455 stack frames => ~9.0k per stack frame
+ //
+ // Windows (Opt+Debug): 900K stack, 235 stack frames => ~3.4k per stack frame
+ //
+ // Linux 32-bit Opt: 1MB stack, 272 stack frames => ~3.8k per stack frame
+ // Linux 64-bit Opt: 2MB stack, 316 stack frames => ~6.5k per stack frame
+ //
+ // We tune the trusted/untrusted quotas for each configuration to achieve our
+ // invariants while attempting to minimize overhead. In contrast, our buffer
+ // between system code and trusted script is a very unscientific 10k.
+ const size_t kSystemCodeBuffer = 10 * 1024;
+
+ // Our "default" stack is what we use in configurations where we don't have
+ // a compelling reason to do things differently. This is effectively 512KB
+ // on 32-bit platforms and 1MB on 64-bit platforms.
+ const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024;
+
+ // Set stack sizes for different configurations. It's probably not great for
+ // the web to base this decision primarily on the default stack size that the
+ // underlying platform makes available, but that seems to be what we do. :-(
+
+#if defined(XP_MACOSX) || defined(DARWIN)
+ // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB,
+ // and give trusted script 180k extra. The stack is huge on mac anyway.
+ const size_t kStackQuota = 7 * 1024 * 1024;
+ const size_t kTrustedScriptBuffer = 180 * 1024;
+#elif defined(MOZ_ASAN)
+ // ASan requires more stack space due to red-zones, so give it double the
+ // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
+ // were not taken at the time of this writing, so we hazard a guess that
+ // ASAN builds have roughly thrice the stack overhead as normal builds.
+ // On normal builds, the largest stack frame size we might encounter is
+ // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k.
+ const size_t kStackQuota = 2 * kDefaultStackQuota;
+ const size_t kTrustedScriptBuffer = 450 * 1024;
+#elif defined(XP_WIN)
+ // 1MB is the default stack size on Windows. We use the /STACK linker flag
+ // to request a larger stack, so we determine the stack size at runtime.
+ const size_t kStackQuota = GetWindowsStackSize();
+ const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 //win64
+ : 120 * 1024; //win32
+ // The following two configurations are linux-only. Given the numbers above,
+ // we use 50k and 100k trusted buffers on 32-bit and 64-bit respectively.
+#elif defined(ANDROID)
+ // Android appears to have 1MB stacks. Allow the use of 3/4 of that size
+ // (768KB on 32-bit), since otherwise we can crash with a stack overflow
+ // when nearing the 1MB limit.
+ const size_t kStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2;
+ const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
+#elif defined(DEBUG)
+ // Bug 803182: account for the 4x difference in the size of js::Interpret
+ // between optimized and debug builds.
+ // XXXbholley - Then why do we only account for 2x of difference?
+ const size_t kStackQuota = 2 * kDefaultStackQuota;
+ const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
+#else
+ const size_t kStackQuota = kDefaultStackQuota;
+ const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
+#endif
+
+ // Avoid an unused variable warning on platforms where we don't use the
+ // default.
+ (void) kDefaultStackQuota;
+
+ JS_SetNativeStackQuota(cx,
+ kStackQuota,
+ kStackQuota - kSystemCodeBuffer,
+ kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
+
+ JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback);
+ JS_SetSizeOfIncludingThisCompartmentCallback(cx, CompartmentSizeOfIncludingThisCallback);
+ JS_SetCompartmentNameCallback(cx, CompartmentNameCallback);
+ mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback);
+ mPrevDoCycleCollectionCallback = JS::SetDoCycleCollectionCallback(cx,
+ DoCycleCollectionCallback);
+ JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr);
+ JS_AddWeakPointerZoneGroupCallback(cx, WeakPointerZoneGroupCallback, this);
+ JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this);
+ JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
+ js::SetPreserveWrapperCallback(cx, PreserveWrapper);
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ if (PseudoStack* stack = mozilla_get_pseudo_stack())
+ stack->sampleContext(cx);
+#endif
+ JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
+ js::SetActivityCallback(cx, ActivityCallback, this);
+ JS_AddInterruptCallback(cx, InterruptCallback);
+ js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
+
+ // 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.
+ UniquePtr<XPCJSSourceHook> hook(new XPCJSSourceHook);
+ js::SetSourceHook(cx, Move(hook));
+
+ // Set up locale information and callbacks for the newly-created context so
+ // that the various toLocaleString() methods, localeCompare(), and other
+ // internationalization APIs work as desired.
+ if (!xpc_LocalizeContext(cx))
+ NS_RUNTIMEABORT("xpc_LocalizeContext failed.");
+
+ // Register memory reporters and distinguished amount functions.
+ RegisterStrongMemoryReporter(new JSMainRuntimeCompartmentsReporter());
+ RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
+ RegisterJSMainRuntimeGCHeapDistinguishedAmount(JSMainRuntimeGCHeapDistinguishedAmount);
+ RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount);
+ RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount);
+ RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount);
+ mozilla::RegisterJSSizeOfTab(JSSizeOfTab);
+
+ // Watch for the JS boolean options.
+ ReloadPrefsCallback(nullptr, this);
+ Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this);
+
+ return NS_OK;
+}
+
+// static
+XPCJSContext*
+XPCJSContext::newXPCJSContext()
+{
+ XPCJSContext* self = new XPCJSContext();
+ nsresult rv = self->Initialize();
+ if (NS_FAILED(rv)) {
+ NS_RUNTIMEABORT("new XPCJSContext failed to initialize.");
+ delete self;
+ return nullptr;
+ }
+
+ if (self->Context() &&
+ self->GetMultiCompartmentWrappedJSMap() &&
+ self->GetWrappedJSClassMap() &&
+ self->GetIID2NativeInterfaceMap() &&
+ self->GetClassInfo2NativeSetMap() &&
+ self->GetNativeSetMap() &&
+ self->GetThisTranslatorMap() &&
+ self->GetDyingWrappedNativeProtoMap() &&
+ self->mWatchdogManager) {
+ return self;
+ }
+
+ NS_RUNTIMEABORT("new XPCJSContext failed to initialize.");
+
+ delete self;
+ return nullptr;
+}
+
+bool
+XPCJSContext::JSContextInitialized(JSContext* cx)
+{
+ JSAutoRequest ar(cx);
+
+ // if it is our first context then we need to generate our string ids
+ if (JSID_IS_VOID(mStrIDs[0])) {
+ RootedString str(cx);
+ for (unsigned i = 0; i < IDX_TOTAL_COUNT; i++) {
+ str = JS_AtomizeAndPinString(cx, mStrings[i]);
+ if (!str) {
+ mStrIDs[0] = JSID_VOID;
+ return false;
+ }
+ mStrIDs[i] = INTERNED_STRING_TO_JSID(cx, str);
+ mStrJSVals[i].setString(str);
+ }
+
+ if (!mozilla::dom::DefineStaticJSVals(cx)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+XPCJSContext::DescribeCustomObjects(JSObject* obj, const js::Class* clasp,
+ char (&name)[72]) const
+{
+ XPCNativeScriptableInfo* si = nullptr;
+
+ if (!IS_PROTO_CLASS(clasp)) {
+ return false;
+ }
+
+ XPCWrappedNativeProto* p =
+ static_cast<XPCWrappedNativeProto*>(xpc_GetJSPrivate(obj));
+ si = p->GetScriptableInfo();
+
+ if (!si) {
+ return false;
+ }
+
+ SprintfLiteral(name, "JS Object (%s - %s)", clasp->name, si->GetJSClass()->name);
+ return true;
+}
+
+bool
+XPCJSContext::NoteCustomGCThingXPCOMChildren(const js::Class* 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 the parent of the JSObject of the tearoff.
+ XPCWrappedNativeTearOff* to =
+ static_cast<XPCWrappedNativeTearOff*>(xpc_GetJSPrivate(obj));
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)->mNative");
+ cb.NoteXPCOMChild(to->GetNative());
+ return true;
+}
+
+void
+XPCJSContext::BeforeProcessTask(bool aMightBlock)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If ProcessNextEvent was called during a Promise "then" callback, we
+ // must process any pending microtasks before blocking in the event loop,
+ // otherwise we may deadlock until an event enters the queue later.
+ if (aMightBlock) {
+ if (Promise::PerformMicroTaskCheckpoint()) {
+ // If any microtask was processed, we post a dummy event in order to
+ // force the ProcessNextEvent call not to block. This is required
+ // to support nested event loops implemented using a pattern like
+ // "while (condition) thread.processNextEvent(true)", in case the
+ // condition is triggered here by a Promise "then" callback.
+
+ NS_DispatchToMainThread(new Runnable());
+ }
+ }
+
+ // Start the slow script timer.
+ mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
+ mSlowScriptSecondHalf = false;
+ mSlowScriptActualWait = mozilla::TimeDuration();
+ mTimeoutAccumulated = false;
+
+ // As we may be entering a nested event loop, we need to
+ // cancel any ongoing performance measurement.
+ js::ResetPerformanceMonitoring(Get()->Context());
+
+ CycleCollectedJSContext::BeforeProcessTask(aMightBlock);
+}
+
+void
+XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth)
+{
+ // Now that we're back to the event loop, reset the slow script checkpoint.
+ mSlowScriptCheckpoint = mozilla::TimeStamp();
+ mSlowScriptSecondHalf = false;
+
+ // Call cycle collector occasionally.
+ MOZ_ASSERT(NS_IsMainThread());
+ nsJSContext::MaybePokeCC();
+
+ CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth);
+
+ // Now that we are certain that the event is complete,
+ // we can flush any ongoing performance measurement.
+ js::FlushPerformanceMonitoring(Get()->Context());
+
+ mozilla::jsipc::AfterProcessTask();
+}
+
+/***************************************************************************/
+
+void
+XPCJSContext::DebugDump(int16_t depth)
+{
+#ifdef DEBUG
+ depth--;
+ XPC_LOG_ALWAYS(("XPCJSContext @ %x", this));
+ XPC_LOG_INDENT();
+ XPC_LOG_ALWAYS(("mJSContext @ %x", Context()));
+
+ XPC_LOG_ALWAYS(("mWrappedJSClassMap @ %x with %d wrapperclasses(s)",
+ mWrappedJSClassMap, mWrappedJSClassMap->Count()));
+ // iterate wrappersclasses...
+ if (depth && mWrappedJSClassMap->Count()) {
+ XPC_LOG_INDENT();
+ for (auto i = mWrappedJSClassMap->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<IID2WrappedJSClassMap::Entry*>(i.Get());
+ entry->value->DebugDump(depth);
+ }
+ XPC_LOG_OUTDENT();
+ }
+
+ // iterate wrappers...
+ XPC_LOG_ALWAYS(("mWrappedJSMap @ %x with %d wrappers(s)",
+ mWrappedJSMap, mWrappedJSMap->Count()));
+ if (depth && mWrappedJSMap->Count()) {
+ XPC_LOG_INDENT();
+ mWrappedJSMap->Dump(depth);
+ XPC_LOG_OUTDENT();
+ }
+
+ XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %x with %d interface(s)",
+ mIID2NativeInterfaceMap,
+ mIID2NativeInterfaceMap->Count()));
+
+ XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %x with %d sets(s)",
+ mClassInfo2NativeSetMap,
+ mClassInfo2NativeSetMap->Count()));
+
+ XPC_LOG_ALWAYS(("mThisTranslatorMap @ %x with %d translator(s)",
+ mThisTranslatorMap, mThisTranslatorMap->Count()));
+
+ XPC_LOG_ALWAYS(("mNativeSetMap @ %x with %d sets(s)",
+ mNativeSetMap, mNativeSetMap->Count()));
+
+ // iterate sets...
+ if (depth && mNativeSetMap->Count()) {
+ XPC_LOG_INDENT();
+ for (auto i = mNativeSetMap->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<NativeSetMap::Entry*>(i.Get());
+ entry->key_value->DebugDump(depth);
+ }
+ XPC_LOG_OUTDENT();
+ }
+
+ XPC_LOG_ALWAYS(("mPendingResult of %x", mPendingResult));
+
+ XPC_LOG_OUTDENT();
+#endif
+}
+
+/***************************************************************************/
+
+void
+XPCRootSetElem::AddToRootSet(XPCRootSetElem** listHead)
+{
+ MOZ_ASSERT(!mSelfp, "Must be not linked");
+
+ mSelfp = listHead;
+ mNext = *listHead;
+ if (mNext) {
+ MOZ_ASSERT(mNext->mSelfp == listHead, "Must be list start");
+ mNext->mSelfp = &mNext;
+ }
+ *listHead = this;
+}
+
+void
+XPCRootSetElem::RemoveFromRootSet()
+{
+ nsXPConnect* xpc = nsXPConnect::XPConnect();
+ JS::PokeGC(xpc->GetContext()->Context());
+
+ MOZ_ASSERT(mSelfp, "Must be linked");
+
+ MOZ_ASSERT(*mSelfp == this, "Link invariant");
+ *mSelfp = mNext;
+ if (mNext)
+ mNext->mSelfp = mSelfp;
+#ifdef DEBUG
+ mSelfp = nullptr;
+ mNext = nullptr;
+#endif
+}
+
+void
+XPCJSContext::AddGCCallback(xpcGCCallback cb)
+{
+ MOZ_ASSERT(cb, "null callback");
+ extraGCCallbacks.AppendElement(cb);
+}
+
+void
+XPCJSContext::RemoveGCCallback(xpcGCCallback cb)
+{
+ MOZ_ASSERT(cb, "null callback");
+ bool found = extraGCCallbacks.RemoveElement(cb);
+ if (!found) {
+ NS_ERROR("Removing a callback which was never added.");
+ }
+}
+
+void
+XPCJSContext::InitSingletonScopes()
+{
+ // This all happens very early, so we don't bother with cx pushing.
+ JSContext* cx = Context();
+ JSAutoRequest ar(cx);
+ RootedValue v(cx);
+ nsresult rv;
+
+ // Create the Unprivileged Junk Scope.
+ SandboxOptions unprivilegedJunkScopeOptions;
+ unprivilegedJunkScopeOptions.sandboxName.AssignLiteral("XPConnect Junk Compartment");
+ unprivilegedJunkScopeOptions.invisibleToDebugger = true;
+ rv = CreateSandboxObject(cx, &v, nullptr, unprivilegedJunkScopeOptions);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ mUnprivilegedJunkScope = js::UncheckedUnwrap(&v.toObject());
+
+ // Create the Privileged Junk Scope.
+ SandboxOptions privilegedJunkScopeOptions;
+ privilegedJunkScopeOptions.sandboxName.AssignLiteral("XPConnect Privileged Junk Compartment");
+ privilegedJunkScopeOptions.invisibleToDebugger = true;
+ privilegedJunkScopeOptions.wantComponents = false;
+ rv = CreateSandboxObject(cx, &v, nsXPConnect::SystemPrincipal(), privilegedJunkScopeOptions);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ mPrivilegedJunkScope = js::UncheckedUnwrap(&v.toObject());
+
+ // Create the Compilation Scope.
+ SandboxOptions compilationScopeOptions;
+ compilationScopeOptions.sandboxName.AssignLiteral("XPConnect Compilation Compartment");
+ compilationScopeOptions.invisibleToDebugger = true;
+ compilationScopeOptions.discardSource = ShouldDiscardSystemSource();
+ rv = CreateSandboxObject(cx, &v, /* principal = */ nullptr, compilationScopeOptions);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ mCompilationScope = js::UncheckedUnwrap(&v.toObject());
+}
+
+void
+XPCJSContext::DeleteSingletonScopes()
+{
+ mUnprivilegedJunkScope = nullptr;
+ mPrivilegedJunkScope = nullptr;
+ mCompilationScope = nullptr;
+}