summaryrefslogtreecommitdiffstats
path: root/dom/base/nsJSEnvironment.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/base/nsJSEnvironment.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/base/nsJSEnvironment.cpp2396
1 files changed, 2396 insertions, 0 deletions
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
new file mode 100644
index 0000000000..cd930688de
--- /dev/null
+++ b/dom/base/nsJSEnvironment.cpp
@@ -0,0 +1,2396 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsError.h"
+#include "nsJSEnvironment.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsPIDOMWindow.h"
+#include "nsDOMCID.h"
+#include "nsIXPConnect.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsReadableUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsJSUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsPresContext.h"
+#include "nsIConsoleService.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsAtom.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsIContent.h"
+#include "nsCycleCollector.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFormatter.h"
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+#include "xpcpublic.h"
+
+#include "jsapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/PropertySpec.h"
+#include "js/SliceBudget.h"
+#include "js/Wrapper.h"
+#include "nsIArray.h"
+#include "CCGCScheduler.h"
+#include "WrapperFactory.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "nsRefreshDriver.h"
+#include "nsJSPrincipals.h"
+#include "AccessCheck.h"
+#include "mozilla/Logging.h"
+#include "prthread.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsViewManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#if defined(MOZ_MEMORY)
+# include "mozmemory.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Thank you Microsoft!
+#ifdef CompareString
+# undef CompareString
+#endif
+
+static JS::GCSliceCallback sPrevGCSliceCallback;
+
+static bool sIncrementalCC = false;
+
+static bool sIsInitialized;
+static bool sShuttingDown;
+
+static CCGCScheduler sScheduler;
+
+struct CycleCollectorStats {
+ constexpr CycleCollectorStats() = default;
+ void Init();
+ void Clear();
+ void PrepareForCycleCollection(TimeStamp aNow);
+ void AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline,
+ TimeStamp aBeginTime,
+ TimeStamp aMaybeAfterGCTime);
+ void AfterCycleCollectionSlice();
+ void AfterSyncForgetSkippable(TimeStamp beginTime);
+ void AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples);
+ void AfterCycleCollection();
+
+ void SendTelemetry(TimeDuration aCCNowDuration, TimeStamp aPrevCCEnd) const;
+ void MaybeLogStats(const CycleCollectorResults& aResults,
+ uint32_t aCleanups) const;
+ void MaybeNotifyStats(const CycleCollectorResults& aResults,
+ TimeDuration aCCNowDuration, uint32_t aCleanups) const;
+
+ // Time the current slice began, including any GC finishing.
+ TimeStamp mBeginSliceTime;
+
+ // Time the previous slice of the current CC ended.
+ TimeStamp mEndSliceTime;
+
+ // Time the current cycle collection began.
+ TimeStamp mBeginTime;
+
+ // The longest GC finishing duration for any slice of the current CC.
+ TimeDuration mMaxGCDuration;
+
+ // True if we ran sync forget skippable in any slice of the current CC.
+ bool mRanSyncForgetSkippable = false;
+
+ // Number of suspected objects at the start of the current CC.
+ uint32_t mSuspected = 0;
+
+ // The longest duration spent on sync forget skippable in any slice of the
+ // current CC.
+ TimeDuration mMaxSkippableDuration;
+
+ // The longest pause of any slice in the current CC.
+ TimeDuration mMaxSliceTime;
+
+ // The longest slice time since ClearMaxCCSliceTime() was called.
+ TimeDuration mMaxSliceTimeSinceClear;
+
+ // The total amount of time spent actually running the current CC.
+ TimeDuration mTotalSliceTime;
+
+ // True if we were locked out by the GC in any slice of the current CC.
+ bool mAnyLockedOut = false;
+
+ // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
+ FILE* mFile = nullptr;
+
+ // In case CC slice was triggered during idle time, set to the end of the idle
+ // period.
+ TimeStamp mIdleDeadline;
+
+ TimeDuration mMinForgetSkippableTime;
+ TimeDuration mMaxForgetSkippableTime;
+ TimeDuration mTotalForgetSkippableTime;
+ uint32_t mForgetSkippableBeforeCC = 0;
+
+ uint32_t mRemovedPurples = 0;
+};
+
+static CycleCollectorStats sCCStats;
+
+static const char* ProcessNameForCollectorLog() {
+ return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
+ : "content";
+}
+
+namespace xpc {
+
+// This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
+// Exceptions, and arbitrary values that were associated with a stack by the
+// JS engine when they were thrown, as specified by exceptionStack.
+//
+// Note that the returned stackObj and stackGlobal are _not_ wrapped into the
+// compartment of exceptionValue.
+void FindExceptionStackForConsoleReport(
+ nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue,
+ JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj,
+ JS::MutableHandle<JSObject*> stackGlobal) {
+ stackObj.set(nullptr);
+ stackGlobal.set(nullptr);
+
+ if (!exceptionValue.isObject()) {
+ // Use the stack provided by the JS engine, if available. This will not be
+ // a wrapper.
+ if (exceptionStack) {
+ stackObj.set(exceptionStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
+ }
+ return;
+ }
+
+ if (win && win->AsGlobal()->IsDying()) {
+ // Pretend like we have no stack, so we don't end up keeping the global
+ // alive via the stack.
+ return;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JSObject*> exceptionObject(rcx, &exceptionValue.toObject());
+ if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
+ // At this point we know exceptionObject is a possibly-wrapped
+ // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
+ // but excStack must be same-compartment with the unwrapped ErrorObject.
+ // Return the ErrorObject's global as stackGlobal. This matches what we do
+ // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
+ // are same-compartment.
+ JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
+ stackObj.set(excStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
+ return;
+ }
+
+ // It is not a JS Exception, try DOM Exception.
+ RefPtr<Exception> exception;
+ UNWRAP_OBJECT(DOMException, exceptionObject, exception);
+ if (!exception) {
+ // Not a DOM Exception, try XPC Exception.
+ UNWRAP_OBJECT(Exception, exceptionObject, exception);
+ if (!exception) {
+ // As above, use the stack provided by the JS engine, if available.
+ if (exceptionStack) {
+ stackObj.set(exceptionStack);
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
+ }
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
+ if (!stack) {
+ return;
+ }
+ JS::Rooted<JS::Value> value(rcx);
+ stack->GetNativeSavedFrame(&value);
+ if (value.isObject()) {
+ stackObj.set(&value.toObject());
+ MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
+ stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
+ return;
+ }
+}
+
+} /* namespace xpc */
+
+static TimeDuration GetCollectionTimeDelta() {
+ static TimeStamp sFirstCollectionTime;
+ TimeStamp now = TimeStamp::Now();
+ if (sFirstCollectionTime) {
+ return now - sFirstCollectionTime;
+ }
+ sFirstCollectionTime = now;
+ return TimeDuration();
+}
+
+class nsJSEnvironmentObserver final : public nsIObserver {
+ ~nsJSEnvironmentObserver() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
+ if (sShuttingDown) {
+ // Don't GC/CC if we're already shutting down.
+ return NS_OK;
+ }
+ nsDependentString data(aData);
+ if (data.EqualsLiteral("low-memory-ongoing")) {
+ // Don't GC/CC if we are in an ongoing low-memory state since its very
+ // slow and it likely won't help us anyway.
+ return NS_OK;
+ }
+ if (data.EqualsLiteral("heap-minimize")) {
+ // heap-minimize notifiers expect this to run synchronously
+ nsJSContext::DoLowMemoryGC();
+ return NS_OK;
+ }
+ if (data.EqualsLiteral("low-memory")) {
+ nsJSContext::SetLowMemoryState(true);
+ }
+ // Asynchronously GC.
+ nsJSContext::LowMemoryGC();
+ }
+ } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
+ nsJSContext::SetLowMemoryState(false);
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
+ sScheduler.UserIsInactive();
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
+ sScheduler.UserIsActive();
+ } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
+ !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
+ !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
+ sShuttingDown = true;
+ sScheduler.Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/****************************************************************
+ ************************** AutoFree ****************************
+ ****************************************************************/
+
+class AutoFree {
+ public:
+ explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
+ ~AutoFree() {
+ if (mPtr) free(mPtr);
+ }
+ void Invalidate() { mPtr = nullptr; }
+
+ private:
+ void* mPtr;
+};
+
+// A utility function for script languages to call. Although it looks small,
+// the use of nsIDocShell and nsPresContext triggers a huge number of
+// dependencies that most languages would not otherwise need.
+// XXXmarkh - This function is mis-placed!
+bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
+ const ErrorEventInit& aErrorEventInit,
+ nsEventStatus* aStatus) {
+ bool called = false;
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
+ nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
+ if (docShell) {
+ RefPtr<nsPresContext> presContext = docShell->GetPresContext();
+
+ static int32_t errorDepth; // Recursion prevention
+ ++errorDepth;
+
+ if (errorDepth < 2) {
+ // Dispatch() must be synchronous for the recursion block
+ // (errorDepth) to work.
+ RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
+ nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ aStatus);
+ called = true;
+ }
+ --errorDepth;
+ }
+ return called;
+}
+
+class ScriptErrorEvent : public Runnable {
+ public:
+ ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
+ xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
+ JS::Handle<JSObject*> aErrorStack)
+ : mozilla::Runnable("ScriptErrorEvent"),
+ mWindow(aWindow),
+ mReport(aReport),
+ mError(aRootingCx, aError),
+ mErrorStack(aRootingCx, aErrorStack) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
+ MOZ_ASSERT(win);
+ MOZ_ASSERT(NS_IsMainThread());
+ // First, notify the DOM that we have a script error, but only if
+ // our window is still the current inner.
+ JS::RootingContext* rootingCx = RootingCx();
+ if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
+ !sHandlingScriptError) {
+ AutoRestore<bool> recursionGuard(sHandlingScriptError);
+ sHandlingScriptError = true;
+
+ RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
+
+ RootedDictionary<ErrorEventInit> init(rootingCx);
+ init.mCancelable = true;
+ init.mFilename = mReport->mFileName;
+ init.mBubbles = true;
+
+ constexpr auto xoriginMsg = u"Script error."_ns;
+ if (!mReport->mIsMuted) {
+ init.mMessage = mReport->mErrorMsg;
+ init.mLineno = mReport->mLineNumber;
+ init.mColno = mReport->mColumn;
+ init.mError = mError;
+ } else {
+ NS_WARNING("Not same origin error!");
+ init.mMessage = xoriginMsg;
+ init.mLineno = 0;
+ }
+
+ RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
+ nsGlobalWindowInner::Cast(win), u"error"_ns, init);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ &status);
+ }
+
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ JS::Rooted<JSObject*> stack(rootingCx);
+ JS::Rooted<JSObject*> stackGlobal(rootingCx);
+ xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
+ &stackGlobal);
+ JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
+ nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
+ mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<xpc::ErrorReport> mReport;
+ JS::PersistentRooted<JS::Value> mError;
+ JS::PersistentRooted<JSObject*> mErrorStack;
+
+ static bool sHandlingScriptError;
+};
+
+bool ScriptErrorEvent::sHandlingScriptError = false;
+
+// This temporarily lives here to avoid code churn. It will go away entirely
+// soon.
+namespace xpc {
+
+void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
+ JS::RootingContext* rootingCx,
+ xpc::ErrorReport* xpcReport,
+ JS::Handle<JS::Value> exception,
+ JS::Handle<JSObject*> exceptionStack) {
+ nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
+ win, rootingCx, xpcReport, exception, exceptionStack));
+}
+
+} /* namespace xpc */
+
+#ifdef DEBUG
+// A couple of useful functions to call when you're debugging.
+nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
+ return xpc::WindowOrNull(obj);
+}
+
+template <typename T>
+void PrintWinURI(T* win) {
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsCOMPtr<Document> doc = win->GetExtantDoc();
+ if (!doc) {
+ printf("No document in the window.\n");
+ return;
+ }
+
+ nsIURI* uri = doc->GetDocumentURI();
+ if (!uri) {
+ printf("Document doesn't have a URI.\n");
+ return;
+ }
+
+ printf("%s\n", uri->GetSpecOrDefault().get());
+}
+
+void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
+
+void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
+
+template <typename T>
+void PrintWinCodebase(T* win) {
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsIPrincipal* prin = win->GetPrincipal();
+ if (!prin) {
+ printf("Window doesn't have principals.\n");
+ return;
+ }
+ if (prin->IsSystemPrincipal()) {
+ printf("No URI, it's the system principal.\n");
+ return;
+ }
+ nsCString spec;
+ prin->GetAsciiSpec(spec);
+ printf("%s\n", spec.get());
+}
+
+void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
+ return PrintWinCodebase(aWin);
+}
+
+void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
+ return PrintWinCodebase(aWin);
+}
+
+void DumpString(const nsAString& str) {
+ printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
+}
+#endif
+
+nsJSContext::nsJSContext(bool aGCOnDestruction,
+ nsIScriptGlobalObject* aGlobalObject)
+ : mWindowProxy(nullptr),
+ mGCOnDestruction(aGCOnDestruction),
+ mGlobalObjectRef(aGlobalObject) {
+ EnsureStatics();
+
+ mProcessingScriptTag = false;
+ HoldJSObjects(this);
+}
+
+nsJSContext::~nsJSContext() {
+ mGlobalObjectRef = nullptr;
+
+ Destroy();
+}
+
+void nsJSContext::Destroy() {
+ if (mGCOnDestruction) {
+ sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
+ }
+
+ DropJSObjects(this);
+}
+
+// QueryInterface implementation for nsJSContext
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
+ tmp->mGCOnDestruction = false;
+ tmp->mWindowProxy = nullptr;
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
+
+#ifdef DEBUG
+bool AtomIsEventHandlerName(nsAtom* aName) {
+ const char16_t* name = aName->GetUTF16String();
+
+ const char16_t* cp;
+ char16_t c;
+ for (cp = name; *cp != '\0'; ++cp) {
+ c = *cp;
+ if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
+ }
+
+ return true;
+}
+#endif
+
+nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
+ // Note: this could probably be simplified somewhat more; see bug 974327
+ // comments 1 and 3.
+ if (!mWindowProxy) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mGlobalObjectRef);
+ return mGlobalObjectRef;
+}
+
+nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
+ const char* aPropName, nsISupports* aArgs) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::RootedVector<JS::Value> args(cx);
+
+ JS::Rooted<JSObject*> global(cx, GetWindowProxy());
+ nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // got the arguments, now attach them.
+
+ for (uint32_t i = 0; i < args.length(); ++i) {
+ if (!JS_WrapValue(cx, args[i])) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
+ if (!array) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+nsresult nsJSContext::ConvertSupportsTojsvals(
+ JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
+ JS::MutableHandleVector<JS::Value> aArgsOut) {
+ nsresult rv = NS_OK;
+
+ // If the array implements nsIJSArgArray, copy the contents and return.
+ nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
+ if (fastArray) {
+ uint32_t argc;
+ JS::Value* argv;
+ rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
+ if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+ }
+
+ // Take the slower path converting each item.
+ // Handle only nsIArray and nsIVariant. nsIArray is only needed for
+ // SetProperty('arguments', ...);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
+
+ if (!aArgs) return NS_OK;
+ uint32_t argCount;
+ // This general purpose function may need to convert an arg array
+ // (window.arguments, event-handler args) and a generic property.
+ nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
+
+ if (argsArray) {
+ rv = argsArray->GetLength(&argCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (argCount == 0) return NS_OK;
+ } else {
+ argCount = 1; // the nsISupports which is not an array
+ }
+
+ // Use the caller's auto guards to release and unroot.
+ if (!aArgsOut.resize(argCount)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (argsArray) {
+ for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
+ nsCOMPtr<nsISupports> arg;
+ JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
+ argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
+ getter_AddRefs(arg));
+ if (!arg) {
+ thisVal.setNull();
+ continue;
+ }
+ nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
+ if (variant != nullptr) {
+ rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
+ } else {
+ // And finally, support the nsISupportsPrimitives supplied
+ // by the AppShell. It generally will pass only strings, but
+ // as we have code for handling all, we may as well use it.
+ rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
+ if (rv == NS_ERROR_NO_INTERFACE) {
+ // something else - probably an event object or similar -
+ // just wrap it.
+#ifdef DEBUG
+ // but first, check its not another nsISupportsPrimitive, as
+ // these are now deprecated for use with script contexts.
+ nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
+ NS_ASSERTION(prim == nullptr,
+ "Don't pass nsISupportsPrimitives - use nsIVariant!");
+#endif
+ JSAutoRealm ar(aCx, aScope);
+ rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
+ }
+ }
+ }
+ } else {
+ nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
+ if (variant) {
+ rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
+ } else {
+ NS_ERROR("Not an array, not an interface?");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ return rv;
+}
+
+// This really should go into xpconnect somewhere...
+nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
+ nsISupports* aArg,
+ JS::Value* aArgv) {
+ MOZ_ASSERT(aArg, "Empty arg");
+
+ nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
+ if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
+
+ uint16_t type;
+ argPrimitive->GetType(&type);
+
+ switch (type) {
+ case nsISupportsPrimitive::TYPE_CSTRING: {
+ nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString data;
+
+ p->GetData(data);
+
+ JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_STRING: {
+ nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsAutoString data;
+
+ p->GetData(data);
+
+ // cast is probably safe since wchar_t and char16_t are expected
+ // to be equivalent; both unsigned 16-bit entities
+ JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRBOOL: {
+ nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ bool data;
+
+ p->GetData(&data);
+
+ aArgv->setBoolean(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT8: {
+ nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint8_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT16: {
+ nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint16_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRUINT32: {
+ nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ uint32_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_CHAR: {
+ nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ char data;
+
+ p->GetData(&data);
+
+ JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
+ NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
+
+ aArgv->setString(str);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRINT16: {
+ nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ int16_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_PRINT32: {
+ nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ int32_t data;
+
+ p->GetData(&data);
+
+ aArgv->setInt32(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_FLOAT: {
+ nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ float data;
+
+ p->GetData(&data);
+
+ *aArgv = ::JS_NumberValue(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_DOUBLE: {
+ nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ double data;
+
+ p->GetData(&data);
+
+ *aArgv = ::JS_NumberValue(data);
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
+ nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
+ NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsISupports> data;
+ nsIID* iid = nullptr;
+
+ p->GetData(getter_AddRefs(data));
+ p->GetDataIID(&iid);
+ NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
+
+ AutoFree iidGuard(iid); // Free iid upon destruction.
+
+ JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
+ JS::Rooted<JS::Value> v(aCx);
+ JSAutoRealm ar(aCx, scope);
+ nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aArgv = v;
+
+ break;
+ }
+ case nsISupportsPrimitive::TYPE_ID:
+ case nsISupportsPrimitive::TYPE_PRUINT64:
+ case nsISupportsPrimitive::TYPE_PRINT64:
+ case nsISupportsPrimitive::TYPE_PRTIME: {
+ NS_WARNING("Unsupported primitive type used");
+ aArgv->setNull();
+ break;
+ }
+ default: {
+ NS_WARNING("Unknown primitive type used");
+ aArgv->setNull();
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+#ifdef MOZ_JPROF
+
+# include <signal.h>
+
+inline bool IsJProfAction(struct sigaction* action) {
+ return (action->sa_sigaction &&
+ (action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
+ (SA_RESTART | SA_SIGINFO));
+}
+
+void NS_JProfStartProfiling();
+void NS_JProfStopProfiling();
+void NS_JProfClearCircular();
+
+static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfStartProfiling();
+ return true;
+}
+
+void NS_JProfStartProfiling() {
+ // Figure out whether we're dealing with SIGPROF, SIGALRM, or
+ // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
+ // JP_RTC_HZ)
+ struct sigaction action;
+
+ // Must check ALRM before PROF since both are enabled for real-time
+ sigaction(SIGALRM, nullptr, &action);
+ // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning real-time jprof profiling.\n");
+ raise(SIGALRM);
+ return;
+ }
+
+ sigaction(SIGPROF, nullptr, &action);
+ // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning process-time jprof profiling.\n");
+ raise(SIGPROF);
+ return;
+ }
+
+ sigaction(SIGPOLL, nullptr, &action);
+ // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
+ if (IsJProfAction(&action)) {
+ // printf("Beginning rtc-based jprof profiling.\n");
+ raise(SIGPOLL);
+ return;
+ }
+
+ printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
+}
+
+static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfStopProfiling();
+ return true;
+}
+
+void NS_JProfStopProfiling() {
+ raise(SIGUSR1);
+ // printf("Stopped jprof profiling.\n");
+}
+
+static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ NS_JProfClearCircular();
+ return true;
+}
+
+void NS_JProfClearCircular() {
+ raise(SIGUSR2);
+ // printf("cleared jprof buffer\n");
+}
+
+static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
+ // Not ideal...
+ NS_JProfStopProfiling();
+ NS_JProfStartProfiling();
+ return true;
+}
+
+static const JSFunctionSpec JProfFunctions[] = {
+ JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
+ JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
+ JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
+ JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
+
+#endif /* defined(MOZ_JPROF) */
+
+nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, aGlobalObj);
+
+#ifdef MOZ_JPROF
+ // Attempt to initialize JProf functions
+ ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
+#endif
+
+ return NS_OK;
+}
+
+bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
+
+void nsJSContext::SetProcessingScriptTag(bool aFlag) {
+ mProcessingScriptTag = aFlag;
+}
+
+// static
+void nsJSContext::SetLowMemoryState(bool aState) {
+ JSContext* cx = danger::GetJSContext();
+ JS::SetLowMemoryState(cx, aState);
+}
+
+static void GarbageCollectImpl(JS::GCReason aReason,
+ nsJSContext::IsShrinking aShrinking,
+ const js::SliceBudget& aBudget) {
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
+ "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
+
+ bool wantIncremental = !aBudget.isUnlimited();
+
+ // We use danger::GetJSContext() since AutoJSAPI will assert if the current
+ // thread's context is null (such as during shutdown).
+ JSContext* cx = danger::GetJSContext();
+
+ if (!nsContentUtils::XPConnect() || !cx) {
+ return;
+ }
+
+ if (sScheduler.InIncrementalGC() && wantIncremental) {
+ // We're in the middle of incremental GC. Do another slice.
+ JS::PrepareForIncrementalGC(cx);
+ JS::IncrementalGCSlice(cx, aReason, aBudget);
+ return;
+ }
+
+ JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
+ ? JS::GCOptions::Shrink
+ : JS::GCOptions::Normal;
+
+ if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
+ sScheduler.SetNeedsFullGC();
+ }
+
+ if (sScheduler.NeedsFullGC()) {
+ JS::PrepareForFullGC(cx);
+ }
+
+ if (wantIncremental) {
+ // Incremental GC slices will be triggered by the GC Runner. If one doesn't
+ // already exist, create it in the GC_SLICE_END callback for the first
+ // slice being executed here.
+ JS::StartIncrementalGC(cx, options, aReason, aBudget);
+ } else {
+ JS::NonIncrementalGC(cx, options, aReason);
+ }
+}
+
+// static
+void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
+ IsShrinking aShrinking) {
+ GarbageCollectImpl(aReason, aShrinking, js::SliceBudget::unlimited());
+}
+
+// static
+void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
+ IsShrinking aShrinking,
+ js::SliceBudget& aBudget) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
+ GarbageCollectImpl(aReason, aShrinking, aBudget);
+}
+
+static void FinishAnyIncrementalGC() {
+ AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
+
+ if (sScheduler.InIncrementalGC()) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ // We're in the middle of an incremental GC, so finish it.
+ JS::PrepareForIncrementalGC(jsapi.cx());
+ JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
+ }
+}
+
+namespace geckoprofiler::markers {
+class CCSliceMarker {
+ public:
+ static constexpr Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("CCSlice");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ bool aIsDuringIdle) {
+ aWriter.BoolProperty("idle", aIsDuringIdle);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ schema.SetAllLabels("{marker.name} (idle={marker.data.idle})");
+ schema.AddKeyLabelFormat("idle", "Idle", MS::Format::Integer);
+ return schema;
+ }
+};
+} // namespace geckoprofiler::markers
+
+static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
+ TimeStamp startTimeStamp = TimeStamp::Now();
+ FinishAnyIncrementalGC();
+
+ uint32_t suspectedBefore = nsCycleCollector_suspectedCount();
+ js::SliceBudget budget =
+ sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
+ bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable();
+ nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
+ earlyForgetSkippable);
+ TimeStamp now = TimeStamp::Now();
+ uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete(
+ now, suspectedBefore, nsCycleCollector_suspectedCount());
+
+ TimeDuration duration = now - startTimeStamp;
+
+ sCCStats.AfterForgetSkippable(duration, removedPurples);
+
+ if (duration.ToSeconds()) {
+ TimeDuration idleDuration;
+ if (!aDeadline.IsNull()) {
+ if (aDeadline < now) {
+ // This slice overflowed the idle period.
+ if (aDeadline > startTimeStamp) {
+ idleDuration = aDeadline - startTimeStamp;
+ }
+ } else {
+ idleDuration = duration;
+ }
+ }
+
+ uint32_t percent =
+ uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
+ }
+
+ PROFILER_MARKER("ForgetSkippable", GCCC,
+ MarkerTiming::IntervalUntilNowFrom(startTimeStamp),
+ CCSliceMarker, !aDeadline.IsNull());
+}
+
+MOZ_ALWAYS_INLINE
+static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
+ MOZ_ASSERT(aEnd >= aStart);
+ return aEnd - aStart;
+}
+
+static TimeDuration TimeUntilNow(TimeStamp start) {
+ if (start.IsNull()) {
+ return TimeDuration();
+ }
+ return TimeBetween(start, TimeStamp::Now());
+}
+
+void CycleCollectorStats::Init() {
+ Clear();
+
+ char* env = getenv("MOZ_CCTIMER");
+ if (!env) {
+ return;
+ }
+ if (strcmp(env, "none") == 0) {
+ mFile = nullptr;
+ } else if (strcmp(env, "stdout") == 0) {
+ mFile = stdout;
+ } else if (strcmp(env, "stderr") == 0) {
+ mFile = stderr;
+ } else {
+ mFile = fopen(env, "a");
+ if (!mFile) {
+ MOZ_CRASH("Failed to open MOZ_CCTIMER log file.");
+ }
+ }
+}
+
+void CycleCollectorStats::Clear() {
+ if (mFile && mFile != stdout && mFile != stderr) {
+ fclose(mFile);
+ }
+ *this = CycleCollectorStats();
+}
+
+void CycleCollectorStats::AfterCycleCollectionSlice() {
+ if (mBeginSliceTime.IsNull()) {
+ // We already called this method from EndCycleCollectionCallback for this
+ // slice.
+ return;
+ }
+
+ mEndSliceTime = TimeStamp::Now();
+ TimeDuration duration = mEndSliceTime - mBeginSliceTime;
+
+ PROFILER_MARKER(
+ "CCSlice", GCCC, MarkerTiming::Interval(mBeginSliceTime, mEndSliceTime),
+ CCSliceMarker, !mIdleDeadline.IsNull() && mIdleDeadline >= mEndSliceTime);
+
+ if (duration.ToSeconds()) {
+ TimeDuration idleDuration;
+ if (!mIdleDeadline.IsNull()) {
+ if (mIdleDeadline < mEndSliceTime) {
+ // This slice overflowed the idle period.
+ if (mIdleDeadline > mBeginSliceTime) {
+ idleDuration = mIdleDeadline - mBeginSliceTime;
+ }
+ } else {
+ idleDuration = duration;
+ }
+ }
+
+ uint32_t percent =
+ uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE,
+ percent);
+ }
+
+ TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
+ mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
+ mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
+ mTotalSliceTime += sliceTime;
+ mBeginSliceTime = TimeStamp();
+}
+
+void CycleCollectorStats::PrepareForCycleCollection(TimeStamp aNow) {
+ mBeginTime = aNow;
+ mSuspected = nsCycleCollector_suspectedCount();
+}
+
+void CycleCollectorStats::AfterPrepareForCycleCollectionSlice(
+ TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime) {
+ mBeginSliceTime = aBeginTime;
+ mIdleDeadline = aDeadline;
+
+ if (!aMaybeAfterGCTime.IsNull()) {
+ mAnyLockedOut = true;
+ mMaxGCDuration = std::max(mMaxGCDuration, aMaybeAfterGCTime - aBeginTime);
+ }
+}
+
+void CycleCollectorStats::AfterSyncForgetSkippable(TimeStamp beginTime) {
+ mMaxSkippableDuration =
+ std::max(mMaxSkippableDuration, TimeUntilNow(beginTime));
+ mRanSyncForgetSkippable = true;
+}
+
+void CycleCollectorStats::AfterForgetSkippable(TimeDuration duration,
+ uint32_t aRemovedPurples) {
+ if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) {
+ mMinForgetSkippableTime = duration;
+ }
+ if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) {
+ mMaxForgetSkippableTime = duration;
+ }
+ mTotalForgetSkippableTime += duration;
+ ++mForgetSkippableBeforeCC;
+
+ mRemovedPurples += aRemovedPurples;
+}
+
+void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration,
+ TimeStamp aPrevCCEnd) const {
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE,
+ mRanSyncForgetSkippable);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL,
+ aCCNowDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE,
+ mMaxSliceTime.ToMilliseconds());
+
+ if (!aPrevCCEnd.IsNull()) {
+ TimeDuration timeBetween = TimeBetween(aPrevCCEnd, mBeginTime);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN,
+ timeBetween.ToSeconds());
+ }
+
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
+ mMaxForgetSkippableTime.ToMilliseconds());
+}
+
+void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
+ uint32_t aCleanups) const {
+ if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) {
+ return;
+ }
+
+ TimeDuration delta = GetCollectionTimeDelta();
+
+ nsCString mergeMsg;
+ if (aResults.mMergedZones) {
+ mergeMsg.AssignLiteral(" merged");
+ }
+
+ nsCString gcMsg;
+ if (aResults.mForcedGC) {
+ gcMsg.AssignLiteral(", forced a GC");
+ }
+
+ const char16_t* kFmt =
+ u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
+ u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
+ u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
+ u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
+ u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
+ nsString msg;
+ nsTextFormatter::ssprintf(
+ msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
+ mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sScheduler.mCCollectedWaitingForGC,
+ sScheduler.mCCollectedZonesWaitingForGC,
+ sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
+ mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
+ mMaxForgetSkippableTime.ToMilliseconds(),
+ mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
+ mTotalForgetSkippableTime.ToMilliseconds(),
+ mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples);
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+ if (mFile) {
+ fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
+ }
+}
+
+void CycleCollectorStats::MaybeNotifyStats(
+ const CycleCollectorResults& aResults, TimeDuration aCCNowDuration,
+ uint32_t aCleanups) const {
+ if (!StaticPrefs::javascript_options_mem_notify()) {
+ return;
+ }
+
+ const char16_t* kJSONFmt =
+ u"{ \"timestamp\": %llu, "
+ u"\"duration\": %.f, "
+ u"\"max_slice_pause\": %.f, "
+ u"\"total_slice_pause\": %.f, "
+ u"\"max_finish_gc_duration\": %.f, "
+ u"\"max_sync_skippable_duration\": %.f, "
+ u"\"suspected\": %lu, "
+ u"\"visited\": { "
+ u"\"RCed\": %lu, "
+ u"\"GCed\": %lu }, "
+ u"\"collected\": { "
+ u"\"RCed\": %lu, "
+ u"\"GCed\": %lu }, "
+ u"\"waiting_for_gc\": %lu, "
+ u"\"zones_waiting_for_gc\": %lu, "
+ u"\"short_living_objects_waiting_for_gc\": %lu, "
+ u"\"forced_gc\": %d, "
+ u"\"forget_skippable\": { "
+ u"\"times_before_cc\": %lu, "
+ u"\"min\": %.f, "
+ u"\"max\": %.f, "
+ u"\"avg\": %.f, "
+ u"\"total\": %.f, "
+ u"\"removed\": %lu } "
+ u"}";
+
+ nsString json;
+ nsTextFormatter::ssprintf(
+ json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
+ mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
+ mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
+ mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sScheduler.mCCollectedWaitingForGC,
+ sScheduler.mCCollectedZonesWaitingForGC,
+ sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
+ mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
+ mMaxForgetSkippableTime.ToMilliseconds(),
+ mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
+ mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
+ json.get());
+ }
+}
+
+// static
+void nsJSContext::CycleCollectNow(CCReason aReason,
+ nsICycleCollectorListener* aListener) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
+
+ PrepareForCycleCollectionSlice(aReason, TimeStamp());
+ nsCycleCollector_collect(aReason, aListener);
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+// static
+void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason,
+ TimeStamp aDeadline) {
+ TimeStamp beginTime = TimeStamp::Now();
+
+ // Before we begin the cycle collection, make sure there is no active GC.
+ TimeStamp afterGCTime;
+ if (sScheduler.InIncrementalGC()) {
+ FinishAnyIncrementalGC();
+ afterGCTime = TimeStamp::Now();
+ }
+
+ if (!sScheduler.IsCollectingCycles()) {
+ sCCStats.PrepareForCycleCollection(beginTime);
+ sScheduler.NoteCCBegin(aReason, beginTime,
+ sCCStats.mForgetSkippableBeforeCC,
+ sCCStats.mSuspected, sCCStats.mRemovedPurples);
+ }
+
+ sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime,
+ afterGCTime);
+}
+
+// static
+void nsJSContext::RunCycleCollectorSlice(CCReason aReason,
+ TimeStamp aDeadline) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PrepareForCycleCollectionSlice(aReason, aDeadline);
+
+ // Decide how long we want to budget for this slice.
+ if (sIncrementalCC) {
+ bool preferShorterSlices;
+ js::SliceBudget budget = sScheduler.ComputeCCSliceBudget(
+ aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime,
+ TimeStamp::Now(), &preferShorterSlices);
+ nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices);
+ } else {
+ js::SliceBudget budget = js::SliceBudget::unlimited();
+ nsCycleCollector_collectSlice(budget, aReason, false);
+ }
+
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+// static
+void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
+
+ PrepareForCycleCollectionSlice(CCReason::API, TimeStamp());
+
+ js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
+ nsCycleCollector_collectSlice(budget, CCReason::API);
+
+ sCCStats.AfterCycleCollectionSlice();
+}
+
+void nsJSContext::ClearMaxCCSliceTime() {
+ sCCStats.mMaxSliceTimeSinceClear = TimeDuration();
+}
+
+uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
+ return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
+}
+
+// static
+void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TimeStamp startTime = TimeStamp::Now();
+ sCCStats.PrepareForCycleCollection(startTime);
+
+ // Run forgetSkippable synchronously to reduce the size of the CC graph. This
+ // is particularly useful if we recently finished a GC.
+ if (sScheduler.IsEarlyForgetSkippable()) {
+ while (sScheduler.IsEarlyForgetSkippable()) {
+ FireForgetSkippable(false, TimeStamp());
+ }
+ sCCStats.AfterSyncForgetSkippable(startTime);
+ }
+
+ if (sShuttingDown) {
+ return;
+ }
+
+ sScheduler.InitCCRunnerStateMachine(
+ mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason);
+ sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
+}
+
+// static
+void nsJSContext::EndCycleCollectionCallback(
+ const CycleCollectorResults& aResults) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sScheduler.KillCCRunner();
+
+ // Update timing information for the current slice before we log it, if
+ // we previously called PrepareForCycleCollectionSlice(). During shutdown
+ // CCs, this won't happen.
+ sCCStats.AfterCycleCollectionSlice();
+
+ TimeStamp endCCTimeStamp = TimeStamp::Now();
+ TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
+ TimeStamp prevCCEnd = sScheduler.GetLastCCEndTime();
+
+ sScheduler.NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime);
+
+ // Log information about the CC via telemetry, JSON and the console.
+
+ sCCStats.SendTelemetry(ccNowDuration, prevCCEnd);
+
+ uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u);
+
+ sCCStats.MaybeLogStats(aResults, cleanups);
+
+ sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups);
+
+ // Update global state to indicate we have just run a cycle collection.
+ sCCStats.Clear();
+
+ // If we need a GC after this CC (typically because lots of GCed objects or
+ // zones have been collected in the CC), schedule it.
+
+ if (sScheduler.NeedsGCAfterCC()) {
+ MOZ_ASSERT(
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration,
+ "A max duration ICC shouldn't reduce GC delay to 0");
+
+ sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr,
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay()) -
+ std::min(ccNowDuration, kMaxICCDuration));
+ }
+#if defined(MOZ_MEMORY)
+ else if (
+ StaticPrefs::
+ dom_memory_foreground_content_processes_have_larger_page_cache()) {
+ jemalloc_free_dirty_pages();
+ }
+#endif
+}
+
+/* static */
+bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
+
+ bool didDoWork = false;
+
+ // The CC/GC scheduler (sScheduler) decides what action(s) to take during
+ // this invocation of the CC runner.
+ //
+ // This may be zero, one, or multiple actions. (Zero is when CC is blocked by
+ // incremental GC, or when the scheduler determined that a CC is no longer
+ // needed.) Loop until the scheduler finishes this invocation by returning
+ // `Yield` in step.mYield.
+ CCRunnerStep step;
+ do {
+ step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(),
+ nsCycleCollector_suspectedCount());
+ switch (step.mAction) {
+ case CCRunnerAction::None:
+ break;
+
+ case CCRunnerAction::MinorGC:
+ JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(),
+ step.mParam.mReason);
+ sScheduler.NoteMinorGCEnd();
+ break;
+
+ case CCRunnerAction::ForgetSkippable:
+ // 'Forget skippable' only, then end this invocation.
+ FireForgetSkippable(bool(step.mParam.mRemoveChildless), aDeadline);
+ break;
+
+ case CCRunnerAction::CleanupContentUnbinder:
+ // Clear content unbinder before the first actual CC slice.
+ Element::ClearContentUnbinder();
+ break;
+
+ case CCRunnerAction::CleanupDeferred:
+ // and if time still permits, perform deferred deletions.
+ nsCycleCollector_doDeferredDeletion();
+ break;
+
+ case CCRunnerAction::CycleCollect:
+ // Cycle collection slice.
+ nsJSContext::RunCycleCollectorSlice(step.mParam.mCCReason, aDeadline);
+ break;
+
+ case CCRunnerAction::StopRunning:
+ // End this CC, either because we have run a cycle collection slice, or
+ // because a CC is no longer needed.
+ sScheduler.KillCCRunner();
+ break;
+ }
+
+ if (step.mAction != CCRunnerAction::None) {
+ didDoWork = true;
+ }
+ } while (step.mYield == CCRunnerYield::Continue);
+
+ return didDoWork;
+}
+
+// static
+bool nsJSContext::HasHadCleanupSinceLastGC() {
+ return sScheduler.IsEarlyForgetSkippable(1);
+}
+
+// static
+void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
+ mozilla::TimeStamp aDeadline) {
+ sScheduler.RunNextCollectorTimer(aReason, aDeadline);
+}
+
+// static
+void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
+ JS::GCReason aReason) {
+ if (!aDocShell || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ BrowsingContext* bc = aDocShell->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+
+ BrowsingContext* root = bc->Top();
+ if (bc == root) {
+ // We don't want to run collectors when loading the top level page.
+ return;
+ }
+
+ nsIDocShell* rootDocShell = root->GetDocShell();
+ if (!rootDocShell) {
+ return;
+ }
+
+ Document* rootDocument = rootDocShell->GetDocument();
+ if (!rootDocument ||
+ rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
+ rootDocument->IsInBackgroundWindow()) {
+ return;
+ }
+
+ PresShell* presShell = rootDocument->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ return;
+ }
+
+ if (!sScheduler.IsUserActive()) {
+ Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
+ // Try to not delay the next RefreshDriver tick, so give a reasonable
+ // deadline for collectors.
+ if (next.isSome()) {
+ sScheduler.RunNextCollectorTimer(aReason, next.value());
+ }
+ }
+}
+
+// static
+void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
+ TimeDuration aDelay) {
+ sScheduler.PokeGC(aReason, aObj, aDelay);
+}
+
+// static
+void nsJSContext::MaybePokeGC() {
+ if (sShuttingDown) {
+ return;
+ }
+
+ JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
+ JS::GCReason reason = JS::WantEagerMinorGC(rt);
+ if (reason != JS::GCReason::NO_REASON) {
+ MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION);
+ sScheduler.PokeMinorGC(reason);
+ }
+
+ // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some
+ // benchmarks. Hopefully that will be worked out and this will check for
+ // whether an eager major GC is needed.
+}
+
+void nsJSContext::DoLowMemoryGC() {
+ if (sShuttingDown) {
+ return;
+ }
+ nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
+ nsJSContext::ShrinkingGC);
+ nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE);
+ if (sScheduler.NeedsGCAfterCC()) {
+ nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
+ nsJSContext::ShrinkingGC);
+ }
+}
+
+// static
+void nsJSContext::LowMemoryGC() {
+ RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
+ CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE);
+ if (!mbPromise) {
+ // Normally when the promise is null it means that IPC failed, that probably
+ // means that something bad happened, don't bother with the GC.
+ return;
+ }
+ mbPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](bool aIgnored) { DoLowMemoryGC(); },
+ [](mozilla::ipc::ResponseRejectReason r) {});
+}
+
+// static
+void nsJSContext::MaybePokeCC() {
+ sScheduler.MaybePokeCC(TimeStamp::Now(), nsCycleCollector_suspectedCount());
+}
+
+static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
+ const JS::GCDescription& aDesc) {
+ NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
+
+ static TimeStamp sCurrentGCStartTime;
+
+ switch (aProgress) {
+ case JS::GC_CYCLE_BEGIN: {
+ // Prevent cycle collections and shrinking during incremental GC.
+ sScheduler.NoteGCBegin(aDesc.reason_);
+ sCurrentGCStartTime = TimeStamp::Now();
+ break;
+ }
+
+ case JS::GC_CYCLE_END: {
+ TimeDuration delta = GetCollectionTimeDelta();
+
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsString gcstats;
+ gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
+ nsAutoString prefix;
+ nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
+ delta.ToSeconds(),
+ ProcessNameForCollectorLog(), getpid());
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ sScheduler.NoteGCEnd();
+
+ // May need to kill the GC runner
+ sScheduler.KillGCRunner();
+
+ nsJSContext::MaybePokeCC();
+
+#if defined(MOZ_MEMORY)
+ bool freeDirty = false;
+#endif
+ if (aDesc.isZone_) {
+ sScheduler.PokeFullGC();
+ } else {
+#if defined(MOZ_MEMORY)
+ freeDirty = true;
+#endif
+ sScheduler.SetNeedsFullGC(false);
+ sScheduler.KillFullGCTimer();
+ }
+
+ if (sScheduler.IsCCNeeded(TimeStamp::Now(),
+ nsCycleCollector_suspectedCount()) !=
+ CCReason::NO_REASON) {
+#if defined(MOZ_MEMORY)
+ // We're likely to free the dirty pages after CC.
+ freeDirty = false;
+#endif
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
+ TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
+
+#if defined(MOZ_MEMORY)
+ if (freeDirty &&
+ StaticPrefs::
+ dom_memory_foreground_content_processes_have_larger_page_cache()) {
+ jemalloc_free_dirty_pages();
+ }
+#endif
+ break;
+ }
+
+ case JS::GC_SLICE_BEGIN:
+ break;
+
+ case JS::GC_SLICE_END:
+ sScheduler.NoteGCSliceEnd(aDesc.lastSliceStart(aCx),
+ aDesc.lastSliceEnd(aCx));
+
+ if (sShuttingDown) {
+ sScheduler.KillGCRunner();
+ } else {
+ // If incremental GC wasn't triggered by GCTimerFired, we may not have a
+ // runner to ensure all the slices are handled. So, create the runner
+ // here.
+ sScheduler.EnsureGCRunner(0);
+ }
+
+ if (sScheduler.IsCCNeeded(TimeStamp::Now(),
+ nsCycleCollector_suspectedCount()) !=
+ CCReason::NO_REASON) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ if (StaticPrefs::javascript_options_mem_log()) {
+ nsString gcstats;
+ gcstats.Adopt(aDesc.formatSliceMessage(aCx));
+ nsAutoString prefix;
+ nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
+ ProcessNameForCollectorLog(), getpid());
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected GCProgress value");
+ }
+
+ if (sPrevGCSliceCallback) {
+ (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
+ }
+}
+
+void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
+ mWindowProxy = aWindowProxy;
+}
+
+JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
+
+void nsJSContext::LikelyShortLivingObjectCreated() {
+ ++sScheduler.mLikelyShortLivingObjectsNeedingGC;
+}
+
+void mozilla::dom::StartupJSEnvironment() {
+ // initialize all our statics, so that we can restart XPCOM
+ sIsInitialized = false;
+ sShuttingDown = false;
+ new (&sScheduler) CCGCScheduler(); // Reset the scheduler state.
+ sCCStats.Init();
+}
+
+static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JS_SetGCParameter(jsapi.cx(), aParam, aValue);
+}
+
+static void ResetGCParameter(JSGCParamKey aParam) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JS_ResetGCParameter(jsapi.cx(), aParam);
+}
+
+static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
+ void* aClosure) {
+ int32_t prefMB = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
+ if (prefB.isValid() && prefB.value() >= 0) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ int32_t prefKB = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
+ if (prefB.isValid() && prefB.value() >= 0) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
+ void* aClosure) {
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref >= 0 && pref < 10000) {
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
+ } else {
+ ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+ }
+}
+
+static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
+ void* aClosure) {
+ bool pref = Preferences::GetBool(aPrefName);
+ SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
+}
+
+static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref > 0 && pref < 100000) {
+ sScheduler.SetActiveIntersliceGCBudget(
+ TimeDuration::FromMilliseconds(pref));
+ SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
+ } else {
+ ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
+ }
+}
+
+static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ bool pref = Preferences::GetBool(aPrefName);
+ sIncrementalCC = pref;
+}
+
+class JSDispatchableRunnable final : public Runnable {
+ ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
+
+ public:
+ explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
+ : mozilla::Runnable("JSDispatchableRunnable"),
+ mDispatchable(aDispatchable) {
+ MOZ_ASSERT(mDispatchable);
+ }
+
+ protected:
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
+ sShuttingDown ? JS::Dispatchable::ShuttingDown
+ : JS::Dispatchable::NotShuttingDown;
+
+ mDispatchable->run(jsapi.cx(), maybeShuttingDown);
+ mDispatchable = nullptr; // mDispatchable may delete itself
+
+ return NS_OK;
+ }
+
+ private:
+ JS::Dispatchable* mDispatchable;
+};
+
+static bool DispatchToEventLoop(void* closure,
+ JS::Dispatchable* aDispatchable) {
+ MOZ_ASSERT(!closure);
+
+ // This callback may execute either on the main thread or a random JS-internal
+ // helper thread. This callback can be called during shutdown so we cannot
+ // simply NS_DispatchToMainThread. Failure during shutdown is expected and
+ // properly handled by the JS engine.
+
+ nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ if (!mainTarget) {
+ return false;
+ }
+
+ RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
+ MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+ return true;
+}
+
+static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MimeType aMimeType,
+ JS::StreamConsumer* aConsumer) {
+ return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
+ nullptr);
+}
+
+static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason,
+ int64_t aMillis) {
+ return sScheduler.CreateGCSliceBudget(
+ mozilla::TimeDuration::FromMilliseconds(aMillis), false, false);
+}
+
+void nsJSContext::EnsureStatics() {
+ if (sIsInitialized) {
+ if (!nsContentUtils::XPConnect()) {
+ MOZ_CRASH();
+ }
+ return;
+ }
+
+ // Let's make sure that our main thread is the same as the xpcom main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
+
+ JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget);
+
+ JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
+ JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
+ FetchUtil::ReportJSStreamError);
+
+ // Set these global xpconnect options...
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
+ "javascript.options.mem.max",
+ (void*)JSGC_MAX_BYTES);
+ Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
+ "javascript.options.mem.nursery.min_kb",
+ (void*)JSGC_MIN_NURSERY_BYTES);
+ Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
+ "javascript.options.mem.nursery.max_kb",
+ (void*)JSGC_MAX_NURSERY_BYTES);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_per_zone",
+ (void*)JSGC_PER_ZONE_GC_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_incremental",
+ (void*)JSGC_INCREMENTAL_GC_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_compacting",
+ (void*)JSGC_COMPACTING_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_parallel_marking",
+ (void*)JSGC_PARALLEL_MARKING_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryGCSliceTimePrefChangedCallback,
+ "javascript.options.mem.gc_incremental_slice_ms");
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.incremental_weakmap",
+ (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_time_limit_ms",
+ (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_low_frequency_heap_growth",
+ (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_large_heap_growth",
+ (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_high_frequency_small_heap_growth",
+ (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackBool,
+ "javascript.options.mem.gc_balanced_heap_limits",
+ (void*)JSGC_BALANCED_HEAP_LIMITS_ENABLED);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_heap_growth_factor",
+ (void*)JSGC_HEAP_GROWTH_FACTOR);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_small_heap_size_max_mb",
+ (void*)JSGC_SMALL_HEAP_SIZE_MAX);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_large_heap_size_min_mb",
+ (void*)JSGC_LARGE_HEAP_SIZE_MIN);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_allocation_threshold_mb",
+ (void*)JSGC_ALLOCATION_THRESHOLD);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_malloc_threshold_base_mb",
+ (void*)JSGC_MALLOC_THRESHOLD_BASE);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_small_heap_incremental_limit",
+ (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_large_heap_incremental_limit",
+ (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_urgent_threshold_mb",
+ (void*)JSGC_URGENT_THRESHOLD_MB);
+
+ Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
+ "dom.cycle_collector.incremental");
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_min_empty_chunk_count",
+ (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_max_empty_chunk_count",
+ (void*)JSGC_MAX_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_helper_thread_ratio",
+ (void*)JSGC_HELPER_THREAD_RATIO);
+
+ Preferences::RegisterCallbackAndCall(
+ SetMemoryPrefChangedCallbackInt,
+ "javascript.options.mem.gc_max_helper_threads",
+ (void*)JSGC_MAX_HELPER_THREADS);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ MOZ_CRASH();
+ }
+
+ nsIObserver* observer = new nsJSEnvironmentObserver();
+ obs->AddObserver(observer, "memory-pressure", false);
+ obs->AddObserver(observer, "user-interaction-inactive", false);
+ obs->AddObserver(observer, "user-interaction-active", false);
+ obs->AddObserver(observer, "quit-application", false);
+ obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(observer, "content-child-will-shutdown", false);
+
+ sIsInitialized = true;
+}
+
+void mozilla::dom::ShutdownJSEnvironment() {
+ sShuttingDown = true;
+ sScheduler.Shutdown();
+}
+
+AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
+ : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
+
+void AsyncErrorReporter::SerializeStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack) {
+ mStackHolder = MakeUnique<SerializedStackHolder>();
+ mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
+}
+
+void AsyncErrorReporter::SetException(JSContext* aCx,
+ JS::Handle<JS::Value> aException) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mException.init(aCx, aException);
+ mHasException = true;
+}
+
+NS_IMETHODIMP AsyncErrorReporter::Run() {
+ AutoJSAPI jsapi;
+ // We're only using this context to deserialize a stack to report to the
+ // console, so the scope we use doesn't matter. Stack frame filtering happens
+ // based on the principal encoded into the frame and the caller compartment,
+ // not the compartment of the frame object, and the console reporting code
+ // will not be using our context, and therefore will not care what compartment
+ // it has entered.
+ DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
+ MOZ_ASSERT(ok, "Problem with system global?");
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> stack(cx);
+ JS::Rooted<JSObject*> stackGlobal(cx);
+ if (mStackHolder) {
+ stack = mStackHolder->ReadStack(cx);
+ if (stack) {
+ stackGlobal = JS::CurrentGlobalOrNull(cx);
+ }
+ }
+
+ JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
+ if (mHasException) {
+ MOZ_ASSERT(NS_IsMainThread());
+ exception = Some(mException);
+ // Remove our reference to the exception.
+ mException.setUndefined();
+ mHasException = false;
+ }
+
+ mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
+ return NS_OK;
+}
+
+// A fast-array class for JS. This class supports both nsIJSScriptArray and
+// nsIArray. If it is JS itself providing and consuming this class, all work
+// can be done via nsIJSScriptArray, and avoid the conversion of elements
+// to/from nsISupports.
+// When consumed by non-JS (eg, another script language), conversion is done
+// on-the-fly.
+class nsJSArgArray final : public nsIJSArgArray {
+ public:
+ nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
+ nsresult* prv);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
+ nsIJSArgArray)
+
+ // nsIArray
+ NS_DECL_NSIARRAY
+
+ // nsIJSArgArray
+ nsresult GetArgs(uint32_t* argc, void** argv) override;
+
+ void ReleaseJSObjects();
+
+ protected:
+ ~nsJSArgArray();
+ JSContext* mContext;
+ JS::Heap<JS::Value>* mArgv;
+ uint32_t mArgc;
+};
+
+nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
+ const JS::Value* argv, nsresult* prv)
+ : mContext(aContext), mArgv(nullptr), mArgc(argc) {
+ // copy the array - we don't know its lifetime, and ours is tied to xpcom
+ // refcounting.
+ if (argc) {
+ mArgv = new (fallible) JS::Heap<JS::Value>[argc];
+ if (!mArgv) {
+ *prv = NS_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+ }
+
+ // Callers are allowed to pass in a null argv even for argc > 0. They can
+ // then use GetArgs to initialize the values.
+ if (argv) {
+ for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
+ }
+
+ if (argc > 0) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ *prv = NS_OK;
+}
+
+nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
+
+void nsJSArgArray::ReleaseJSObjects() {
+ delete[] mArgv;
+
+ if (mArgc > 0) {
+ mArgc = 0;
+ mozilla::DropJSObjects(this);
+ }
+}
+
+// QueryInterface implementation for nsJSArgArray
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
+ tmp->ReleaseJSObjects();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
+ if (tmp->mArgv) {
+ for (uint32_t i = 0; i < tmp->mArgc; ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
+ NS_INTERFACE_MAP_ENTRY(nsIArray)
+ NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
+
+nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
+ *argv = (void*)mArgv;
+ *argc = mArgc;
+ return NS_OK;
+}
+
+// nsIArray impl
+NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
+ *aLength = mArgc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
+ void** result) {
+ *result = nullptr;
+ if (index >= mArgc) return NS_ERROR_INVALID_ARG;
+
+ if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
+ uuid.Equals(NS_GET_IID(nsISupports))) {
+ // Have to copy a Heap into a Rooted to work with it.
+ JS::Rooted<JS::Value> val(mContext, mArgv[index]);
+ return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
+ (nsIVariant**)result);
+ }
+ NS_WARNING("nsJSArgArray only handles nsIVariant");
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
+ uint8_t aArgc,
+ nsISimpleEnumerator** aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
+ nsISimpleEnumerator** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// The factory function
+nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
+ const JS::Value* argv, nsIJSArgArray** aArray) {
+ nsresult rv;
+ nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ ret.forget(aArray);
+ return NS_OK;
+}