summaryrefslogtreecommitdiffstats
path: root/dom/base/nsJSEnvironment.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsJSEnvironment.cpp')
-rw-r--r--dom/base/nsJSEnvironment.cpp2812
1 files changed, 2812 insertions, 0 deletions
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
new file mode 100644
index 000000000..b273d00c9
--- /dev/null
+++ b/dom/base/nsJSEnvironment.cpp
@@ -0,0 +1,2812 @@
+/* -*- 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 "nsIDOMChromeWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsDOMCID.h"
+#include "nsIServiceManager.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 "nsIScriptError.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPrompt.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIAtom.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsIContent.h"
+#include "nsCycleCollector.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULRuntime.h"
+#include "nsTextFormatter.h"
+#include "ScriptSettings.h"
+
+#include "xpcpublic.h"
+
+#include "jsapi.h"
+#include "jswrapper.h"
+#include "js/SliceBudget.h"
+#include "nsIArray.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "prmem.h"
+#include "WrapperFactory.h"
+#include "nsGlobalWindow.h"
+#include "nsScriptNameSpaceManager.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "nsAXPCNativeCallContext.h"
+#include "mozilla/CycleCollectedJSContext.h"
+
+#include "nsJSPrincipals.h"
+
+#ifdef XP_MACOSX
+// AssertMacros.h defines 'check' and conflicts with AccessCheck.h
+#undef check
+#endif
+#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/asmjscache/AsmJSCache.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ContentEvents.h"
+
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+const size_t gStackSize = 8192;
+
+// Thank you Microsoft!
+#ifdef CompareString
+#undef CompareString
+#endif
+
+#define NS_SHRINK_GC_BUFFERS_DELAY 4000 // ms
+
+// The amount of time we wait from the first request to GC to actually
+// doing the first GC.
+#define NS_FIRST_GC_DELAY 10000 // ms
+
+#define NS_FULL_GC_DELAY 60000 // ms
+
+// The default amount of time to wait from the user being idle to starting a
+// shrinking GC.
+#define NS_DEAULT_INACTIVE_GC_DELAY 300000 // ms
+
+// Maximum amount of time that should elapse between incremental GC slices
+#define NS_INTERSLICE_GC_DELAY 100 // ms
+
+// If we haven't painted in 100ms, we allow for a longer GC budget
+#define NS_INTERSLICE_GC_BUDGET 40 // ms
+
+// The amount of time we wait between a request to CC (after GC ran)
+// and doing the actual CC.
+#define NS_CC_DELAY 6000 // ms
+
+#define NS_CC_SKIPPABLE_DELAY 250 // ms
+
+// Maximum amount of time that should elapse between incremental CC slices
+static const int64_t kICCIntersliceDelay = 32; // ms
+
+// Time budget for an incremental CC slice
+static const int64_t kICCSliceBudget = 5; // ms
+
+// Maximum total duration for an ICC
+static const uint32_t kMaxICCDuration = 2000; // ms
+
+// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
+// objects in the purple buffer.
+#define NS_CC_FORCED (2 * 60 * PR_USEC_PER_SEC) // 2 min
+#define NS_CC_FORCED_PURPLE_LIMIT 10
+
+// Don't allow an incremental GC to lock out the CC for too long.
+#define NS_MAX_CC_LOCKEDOUT_TIME (30 * PR_USEC_PER_SEC) // 30 seconds
+
+// Trigger a CC if the purple buffer exceeds this size when we check it.
+#define NS_CC_PURPLE_LIMIT 200
+
+// Large value used to specify that a script should run essentially forever
+#define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
+
+// if you add statics here, add them to the list in StartupJSEnvironment
+
+static nsITimer *sGCTimer;
+static nsITimer *sShrinkingGCTimer;
+static nsITimer *sCCTimer;
+static nsITimer *sICCTimer;
+static nsITimer *sFullGCTimer;
+static nsITimer *sInterSliceGCTimer;
+
+static TimeStamp sLastCCEndTime;
+
+static bool sCCLockedOut;
+static PRTime sCCLockedOutTime;
+
+static JS::GCSliceCallback sPrevGCSliceCallback;
+
+static bool sHasRunGC;
+
+// The number of currently pending document loads. This count isn't
+// guaranteed to always reflect reality and can't easily as we don't
+// have an easy place to know when a load ends or is interrupted in
+// all cases. This counter also gets reset if we end up GC'ing while
+// we're waiting for a slow page to load. IOW, this count may be 0
+// even when there are pending loads.
+static uint32_t sPendingLoadCount;
+static bool sLoadingInProgress;
+
+static uint32_t sCCollectedWaitingForGC;
+static uint32_t sCCollectedZonesWaitingForGC;
+static uint32_t sLikelyShortLivingObjectsNeedingGC;
+static bool sPostGCEventsToConsole;
+static bool sPostGCEventsToObserver;
+static int32_t sCCTimerFireCount = 0;
+static uint32_t sMinForgetSkippableTime = UINT32_MAX;
+static uint32_t sMaxForgetSkippableTime = 0;
+static uint32_t sTotalForgetSkippableTime = 0;
+static uint32_t sRemovedPurples = 0;
+static uint32_t sForgetSkippableBeforeCC = 0;
+static uint32_t sPreviousSuspectedCount = 0;
+static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
+static bool sNeedsFullCC = false;
+static bool sNeedsFullGC = false;
+static bool sNeedsGCAfterCC = false;
+static bool sIncrementalCC = false;
+static bool sDidPaintAfterPreviousICCSlice = false;
+
+static nsScriptNameSpaceManager *gNameSpaceManager;
+
+static PRTime sFirstCollectionTime;
+
+static JSContext* sContext;
+
+static bool sIsInitialized;
+static bool sDidShutdown;
+static bool sShuttingDown;
+static int32_t sContextCount;
+
+static nsIScriptSecurityManager *sSecurityManager;
+
+// nsJSEnvironmentObserver observes the memory-pressure notifications
+// and forces a garbage collection and cycle collection when it happens, if
+// the appropriate pref is set.
+
+static bool sGCOnMemoryPressure;
+
+// nsJSEnvironmentObserver observes the user-interaction-inactive notifications
+// and triggers a shrinking a garbage collection if the user is still inactive
+// after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set.
+
+static bool sCompactOnUserInactive;
+static uint32_t sCompactOnUserInactiveDelay = NS_DEAULT_INACTIVE_GC_DELAY;
+static bool sIsCompactingOnUserInactive = false;
+
+// In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more
+// aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps
+// us from triggering expensive full collections too frequently.
+static int32_t sExpensiveCollectorPokes = 0;
+static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5;
+
+static const char*
+ProcessNameForCollectorLog()
+{
+ return XRE_GetProcessType() == GeckoProcessType_Default ?
+ "default" : "content";
+}
+
+namespace xpc {
+
+// This handles JS Exceptions (via ExceptionStackOrNull), as well as DOM and XPC
+// Exceptions.
+//
+// Note that the returned object is _not_ wrapped into the compartment of
+// exceptionValue.
+JSObject*
+FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
+ JS::HandleValue exceptionValue)
+{
+ if (!exceptionValue.isObject()) {
+ return nullptr;
+ }
+
+ if (win && win->InnerObjectsFreed()) {
+ // Pretend like we have no stack, so we don't end up keeping the global
+ // alive via the stack.
+ return nullptr;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::RootedObject exceptionObject(rcx, &exceptionValue.toObject());
+ JSObject* stackObject = ExceptionStackOrNull(exceptionObject);
+ if (stackObject) {
+ return stackObject;
+ }
+
+ // 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) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
+ if (!stack) {
+ return nullptr;
+ }
+ JS::RootedValue value(rcx);
+ stack->GetNativeSavedFrame(&value);
+ if (value.isObject()) {
+ return &value.toObject();
+ }
+ return nullptr;
+}
+
+} /* namespace xpc */
+
+static PRTime
+GetCollectionTimeDelta()
+{
+ PRTime now = PR_Now();
+ if (sFirstCollectionTime) {
+ return now - sFirstCollectionTime;
+ }
+ sFirstCollectionTime = now;
+ return 0;
+}
+
+static void
+KillTimers()
+{
+ nsJSContext::KillGCTimer();
+ nsJSContext::KillShrinkingGCTimer();
+ nsJSContext::KillCCTimer();
+ nsJSContext::KillICCTimer();
+ nsJSContext::KillFullGCTimer();
+ nsJSContext::KillInterSliceGCTimer();
+}
+
+// If we collected a substantial amount of cycles, poke the GC since more objects
+// might be unreachable now.
+static bool
+NeedsGCAfterCC()
+{
+ return sCCollectedWaitingForGC > 250 ||
+ sCCollectedZonesWaitingForGC > 0 ||
+ sLikelyShortLivingObjectsNeedingGC > 2500 ||
+ sNeedsGCAfterCC;
+}
+
+class nsJSEnvironmentObserver final : public nsIObserver
+{
+ ~nsJSEnvironmentObserver() {}
+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 (sGCOnMemoryPressure) {
+ if(StringBeginsWith(nsDependentString(aData),
+ NS_LITERAL_STRING("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;
+ }
+ nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
+ nsJSContext::NonIncrementalGC,
+ nsJSContext::ShrinkingGC);
+ nsJSContext::CycleCollectNow();
+ if (NeedsGCAfterCC()) {
+ nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
+ nsJSContext::NonIncrementalGC,
+ nsJSContext::ShrinkingGC);
+ }
+ }
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
+ if (sCompactOnUserInactive) {
+ nsJSContext::PokeShrinkingGC();
+ }
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
+ nsJSContext::KillShrinkingGCTimer();
+ if (sIsCompactingOnUserInactive) {
+ JS::AbortIncrementalGC(sContext);
+ }
+ MOZ_ASSERT(!sIsCompactingOnUserInactive);
+ } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
+ !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ sShuttingDown = true;
+ KillTimers();
+ }
+
+ return NS_OK;
+}
+
+/****************************************************************
+ ************************** AutoFree ****************************
+ ****************************************************************/
+
+class AutoFree {
+public:
+ explicit AutoFree(void* aPtr) : mPtr(aPtr) {
+ }
+ ~AutoFree() {
+ if (mPtr)
+ free(mPtr);
+ }
+ void Invalidate() {
+ mPtr = 0;
+ }
+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(getter_AddRefs(presContext));
+
+ 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(nsGlobalWindow::Cast(win),
+ NS_LITERAL_STRING("error"),
+ 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)
+ : mWindow(aWindow)
+ , mReport(aReport)
+ , mError(aRootingCx, aError)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ 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(getter_AddRefs(presContext));
+
+ RootedDictionary<ErrorEventInit> init(rootingCx);
+ init.mCancelable = true;
+ init.mFilename = mReport->mFileName;
+ init.mBubbles = true;
+
+ NS_NAMED_LITERAL_STRING(xoriginMsg, "Script error.");
+ 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(nsGlobalWindow::Cast(win),
+ NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ &status);
+ }
+
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ JS::Rooted<JSObject*> stack(rootingCx,
+ xpc::FindExceptionStackForConsoleReport(win, mError));
+ mReport->LogToConsoleWithStack(stack);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<xpc::ErrorReport> mReport;
+ JS::PersistentRootedValue mError;
+
+ 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)
+{
+ nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception));
+}
+
+} /* namespace xpc */
+
+#ifdef DEBUG
+// A couple of useful functions to call when you're debugging.
+nsGlobalWindow *
+JSObject2Win(JSObject *obj)
+{
+ return xpc::WindowOrNull(obj);
+}
+
+void
+PrintWinURI(nsGlobalWindow *win)
+{
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> 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
+PrintWinCodebase(nsGlobalWindow *win)
+{
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsIPrincipal *prin = win->GetPrincipal();
+ if (!prin) {
+ printf("Window doesn't have principals.\n");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ prin->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ printf("No URI, maybe the system principal.\n");
+ return;
+ }
+
+ printf("%s\n", uri->GetSpecOrDefault().get());
+}
+
+void
+DumpString(const nsAString &str)
+{
+ printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
+}
+#endif
+
+#define JS_OPTIONS_DOT_STR "javascript.options."
+
+static const char js_options_dot_str[] = JS_OPTIONS_DOT_STR;
+
+nsJSContext::nsJSContext(bool aGCOnDestruction,
+ nsIScriptGlobalObject* aGlobalObject)
+ : mWindowProxy(nullptr)
+ , mGCOnDestruction(aGCOnDestruction)
+ , mGlobalObjectRef(aGlobalObject)
+{
+ EnsureStatics();
+
+ ++sContextCount;
+
+ mIsInitialized = false;
+ mProcessingScriptTag = false;
+ HoldJSObjects(this);
+}
+
+nsJSContext::~nsJSContext()
+{
+ mGlobalObjectRef = nullptr;
+
+ Destroy();
+
+ --sContextCount;
+
+ if (!sContextCount && sDidShutdown) {
+ // The last context is being deleted, and we're already in the
+ // process of shutting down, release the security manager.
+
+ NS_IF_RELEASE(sSecurityManager);
+ }
+}
+
+void
+nsJSContext::Destroy()
+{
+ if (mGCOnDestruction) {
+ PokeGC(JS::gcreason::NSJSCONTEXT_DESTROY);
+ }
+
+ 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->mIsInitialized = false;
+ 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_SCRIPT_OBJECTS
+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(nsIAtom *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::InitContext()
+{
+ // Make sure callers of this use
+ // WillInitializeContext/DidInitializeContext around this call.
+ NS_ENSURE_TRUE(!mIsInitialized, NS_ERROR_ALREADY_INITIALIZED);
+
+ // XXXbz Is there still a point to this function?
+ return NS_OK;
+}
+
+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::AutoValueVector args(cx);
+
+ JS::Rooted<JSObject*> global(cx, GetWindowProxy());
+ nsresult rv =
+ ConvertSupportsTojsvals(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(nsISupports* aArgs,
+ JS::Handle<JSObject*> aScope,
+ JS::AutoValueVector& 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);
+ AutoJSContext cx;
+
+ 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(cx, 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(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
+ JSAutoCompartment ac(cx, aScope);
+ rv = nsContentUtils::WrapNative(cx, arg, thisVal);
+ }
+ }
+ }
+ } else {
+ nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
+ if (variant) {
+ rv = xpc->VariantToJS(cx, 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(nsISupports *aArg, JS::Value *aArgv)
+{
+ NS_PRECONDITION(aArg, "Empty arg");
+
+ nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
+ if (!argPrimitive)
+ return NS_ERROR_NO_INTERFACE;
+
+ AutoJSContext cx;
+ 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(cx, 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(cx, 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(cx, &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(cx, GetWindowProxy());
+ JS::Rooted<JS::Value> v(cx);
+ JSAutoCompartment ac(cx, scope);
+ nsresult rv = nsContentUtils::WrapNative(cx, 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 :
+ case nsISupportsPrimitive::TYPE_VOID : {
+ 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_FS("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
+ JS_FS("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
+ JS_FS("JProfClearCircular", JProfClearCircularJS, 0, 0),
+ JS_FS("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();
+ JSAutoCompartment ac(cx, aGlobalObj);
+
+ // Attempt to initialize profiling functions
+ ::JS_DefineProfilingFunctions(cx, aGlobalObj);
+
+#ifdef MOZ_JPROF
+ // Attempt to initialize JProf functions
+ ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
+#endif
+
+ return NS_OK;
+}
+
+void
+nsJSContext::WillInitializeContext()
+{
+ mIsInitialized = false;
+}
+
+void
+nsJSContext::DidInitializeContext()
+{
+ mIsInitialized = true;
+}
+
+bool
+nsJSContext::IsContextInitialized()
+{
+ return mIsInitialized;
+}
+
+bool
+nsJSContext::GetProcessingScriptTag()
+{
+ return mProcessingScriptTag;
+}
+
+void
+nsJSContext::SetProcessingScriptTag(bool aFlag)
+{
+ mProcessingScriptTag = aFlag;
+}
+
+void
+FullGCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ nsJSContext::KillFullGCTimer();
+ MOZ_ASSERT(!aClosure, "Don't pass a closure to FullGCTimerFired");
+ nsJSContext::GarbageCollectNow(JS::gcreason::FULL_GC_TIMER,
+ nsJSContext::IncrementalGC);
+}
+
+//static
+void
+nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
+ IsIncremental aIncremental,
+ IsShrinking aShrinking,
+ int64_t aSliceMillis)
+{
+ PROFILER_LABEL("nsJSContext", "GarbageCollectNow",
+ js::ProfileEntry::Category::GC);
+
+ MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
+
+ KillGCTimer();
+
+ // Reset sPendingLoadCount in case the timer that fired was a
+ // timer we scheduled due to a normal GC timer firing while
+ // documents were loading. If this happens we're waiting for a
+ // document that is taking a long time to load, and we effectively
+ // ignore the fact that the currently loading documents are still
+ // loading and move on as if they weren't.
+ sPendingLoadCount = 0;
+ sLoadingInProgress = false;
+
+ if (!nsContentUtils::XPConnect() || !sContext) {
+ return;
+ }
+
+ if (sCCLockedOut && aIncremental == IncrementalGC) {
+ // We're in the middle of incremental GC. Do another slice.
+ JS::PrepareForIncrementalGC(sContext);
+ JS::IncrementalGCSlice(sContext, aReason, aSliceMillis);
+ return;
+ }
+
+ JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;
+
+ if (sNeedsFullGC || aReason != JS::gcreason::CC_WAITING) {
+ sNeedsFullGC = false;
+ JS::PrepareForFullGC(sContext);
+ } else {
+ CycleCollectedJSContext::Get()->PrepareWaitingZonesForGC();
+ }
+
+ if (aIncremental == IncrementalGC) {
+ JS::StartIncrementalGC(sContext, gckind, aReason, aSliceMillis);
+ } else {
+ JS::GCForReason(sContext, gckind, aReason);
+ }
+}
+
+static void
+FinishAnyIncrementalGC()
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GC);
+
+ if (sCCLockedOut) {
+ // We're in the middle of an incremental GC, so finish it.
+ JS::PrepareForIncrementalGC(sContext);
+ JS::FinishIncrementalGC(sContext, JS::gcreason::CC_FORCED);
+ }
+}
+
+static void
+FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless)
+{
+ PRTime startTime = PR_Now();
+ FinishAnyIncrementalGC();
+ bool earlyForgetSkippable =
+ sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS;
+ nsCycleCollector_forgetSkippable(aRemoveChildless, earlyForgetSkippable);
+ sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
+ ++sCleanupsSinceLastGC;
+ PRTime delta = PR_Now() - startTime;
+ if (sMinForgetSkippableTime > delta) {
+ sMinForgetSkippableTime = delta;
+ }
+ if (sMaxForgetSkippableTime < delta) {
+ sMaxForgetSkippableTime = delta;
+ }
+ sTotalForgetSkippableTime += delta;
+ sRemovedPurples += (aSuspected - sPreviousSuspectedCount);
+ ++sForgetSkippableBeforeCC;
+}
+
+MOZ_ALWAYS_INLINE
+static uint32_t
+TimeBetween(TimeStamp start, TimeStamp end)
+{
+ MOZ_ASSERT(end >= start);
+ return (uint32_t) ((end - start).ToMilliseconds());
+}
+
+static uint32_t
+TimeUntilNow(TimeStamp start)
+{
+ if (start.IsNull()) {
+ return 0;
+ }
+ return TimeBetween(start, TimeStamp::Now());
+}
+
+struct CycleCollectorStats
+{
+ constexpr CycleCollectorStats() :
+ mMaxGCDuration(0), mRanSyncForgetSkippable(false), mSuspected(0),
+ mMaxSkippableDuration(0), mMaxSliceTime(0), mMaxSliceTimeSinceClear(0),
+ mTotalSliceTime(0), mAnyLockedOut(false), mExtraForgetSkippableCalls(0),
+ mFile(nullptr) {}
+
+ void Init()
+ {
+ Clear();
+ mMaxSliceTimeSinceClear = 0;
+
+ 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 Clear()
+ {
+ if (mFile && mFile != stdout && mFile != stderr) {
+ fclose(mFile);
+ }
+ mBeginSliceTime = TimeStamp();
+ mEndSliceTime = TimeStamp();
+ mBeginTime = TimeStamp();
+ mMaxGCDuration = 0;
+ mRanSyncForgetSkippable = false;
+ mSuspected = 0;
+ mMaxSkippableDuration = 0;
+ mMaxSliceTime = 0;
+ mTotalSliceTime = 0;
+ mAnyLockedOut = false;
+ mExtraForgetSkippableCalls = 0;
+ }
+
+ void PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls = 0);
+
+ void FinishCycleCollectionSlice()
+ {
+ if (mBeginSliceTime.IsNull()) {
+ // We already called this method from EndCycleCollectionCallback for this slice.
+ return;
+ }
+
+ mEndSliceTime = TimeStamp::Now();
+ uint32_t sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
+ mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
+ mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
+ mTotalSliceTime += sliceTime;
+ mBeginSliceTime = TimeStamp();
+ MOZ_ASSERT(mExtraForgetSkippableCalls == 0, "Forget to reset extra forget skippable calls?");
+ }
+
+ void RunForgetSkippable();
+
+ // 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.
+ uint32_t mMaxGCDuration;
+
+ // True if we ran sync forget skippable in any slice of the current CC.
+ bool mRanSyncForgetSkippable;
+
+ // Number of suspected objects at the start of the current CC.
+ uint32_t mSuspected;
+
+ // The longest duration spent on sync forget skippable in any slice of the
+ // current CC.
+ uint32_t mMaxSkippableDuration;
+
+ // The longest pause of any slice in the current CC.
+ uint32_t mMaxSliceTime;
+
+ // The longest slice time since ClearMaxCCSliceTime() was called.
+ uint32_t mMaxSliceTimeSinceClear;
+
+ // The total amount of time spent actually running the current CC.
+ uint32_t mTotalSliceTime;
+
+ // True if we were locked out by the GC in any slice of the current CC.
+ bool mAnyLockedOut;
+
+ int32_t mExtraForgetSkippableCalls;
+
+ // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
+ FILE* mFile;
+};
+
+CycleCollectorStats gCCStats;
+
+void
+CycleCollectorStats::PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls)
+{
+ mBeginSliceTime = TimeStamp::Now();
+
+ // Before we begin the cycle collection, make sure there is no active GC.
+ if (sCCLockedOut) {
+ mAnyLockedOut = true;
+ FinishAnyIncrementalGC();
+ uint32_t gcTime = TimeBetween(mBeginSliceTime, TimeStamp::Now());
+ mMaxGCDuration = std::max(mMaxGCDuration, gcTime);
+ }
+
+ mExtraForgetSkippableCalls = aExtraForgetSkippableCalls;
+}
+
+void
+CycleCollectorStats::RunForgetSkippable()
+{
+ // Run forgetSkippable synchronously to reduce the size of the CC graph. This
+ // is particularly useful if we recently finished a GC.
+ if (mExtraForgetSkippableCalls >= 0) {
+ TimeStamp beginForgetSkippable = TimeStamp::Now();
+ bool ranSyncForgetSkippable = false;
+ while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
+ FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
+ ranSyncForgetSkippable = true;
+ }
+
+ for (int32_t i = 0; i < mExtraForgetSkippableCalls; ++i) {
+ FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
+ ranSyncForgetSkippable = true;
+ }
+
+ if (ranSyncForgetSkippable) {
+ mMaxSkippableDuration =
+ std::max(mMaxSkippableDuration, TimeUntilNow(beginForgetSkippable));
+ mRanSyncForgetSkippable = true;
+ }
+
+ }
+ mExtraForgetSkippableCalls = 0;
+}
+
+//static
+void
+nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
+ int32_t aExtraForgetSkippableCalls)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "CycleCollectNow",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice(aExtraForgetSkippableCalls);
+ nsCycleCollector_collect(aListener);
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+//static
+void
+nsJSContext::RunCycleCollectorSlice()
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "RunCycleCollectorSlice",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice();
+
+ // Decide how long we want to budget for this slice. By default,
+ // use an unlimited budget.
+ js::SliceBudget budget = js::SliceBudget::unlimited();
+
+ if (sIncrementalCC) {
+ if (gCCStats.mBeginTime.IsNull()) {
+ // If no CC is in progress, use the standard slice time.
+ budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
+ } else {
+ TimeStamp now = TimeStamp::Now();
+
+ // Only run a limited slice if we're within the max running time.
+ if (TimeBetween(gCCStats.mBeginTime, now) < kMaxICCDuration) {
+ float sliceMultiplier = std::max(TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay, 1.0f);
+ budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget * sliceMultiplier));
+ }
+ }
+ }
+
+ nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice);
+ sDidPaintAfterPreviousICCSlice = false;
+
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+//static
+void
+nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "RunCycleCollectorWorkSlice",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice();
+
+ js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
+ nsCycleCollector_collectSlice(budget);
+
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+void
+nsJSContext::ClearMaxCCSliceTime()
+{
+ gCCStats.mMaxSliceTimeSinceClear = 0;
+}
+
+uint32_t
+nsJSContext::GetMaxCCSliceTimeSinceClear()
+{
+ return gCCStats.mMaxSliceTimeSinceClear;
+}
+
+static void
+ICCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ if (sDidShutdown) {
+ return;
+ }
+
+ // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
+ // to synchronously finish the GC, which is bad.
+
+ if (sCCLockedOut) {
+ PRTime now = PR_Now();
+ if (sCCLockedOutTime == 0) {
+ sCCLockedOutTime = now;
+ return;
+ }
+ if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
+ return;
+ }
+ }
+
+ nsJSContext::RunCycleCollectorSlice();
+}
+
+//static
+void
+nsJSContext::BeginCycleCollectionCallback()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime;
+ gCCStats.mSuspected = nsCycleCollector_suspectedCount();
+
+ KillCCTimer();
+
+ gCCStats.RunForgetSkippable();
+
+ MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
+
+ // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
+ // an incremental collection, and we want to be sure to finish it.
+ CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
+ if (sICCTimer) {
+ sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+ kICCIntersliceDelay,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "ICCTimerFired");
+ }
+}
+
+static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
+
+//static
+void
+nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsJSContext::KillICCTimer();
+
+ // Update timing information for the current slice before we log it, if
+ // we previously called PrepareForCycleCollectionSlice(). During shutdown
+ // CCs, this won't happen.
+ gCCStats.FinishCycleCollectionSlice();
+
+ sCCollectedWaitingForGC += aResults.mFreedGCed;
+ sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
+
+ TimeStamp endCCTimeStamp = TimeStamp::Now();
+ uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
+
+ if (NeedsGCAfterCC()) {
+ PokeGC(JS::gcreason::CC_WAITING,
+ NS_GC_DELAY - std::min(ccNowDuration, kMaxICCDuration));
+ }
+
+ // Log information about the CC via telemetry, JSON and the console.
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, gCCStats.mMaxSliceTime);
+
+ if (!sLastCCEndTime.IsNull()) {
+ // TimeBetween returns milliseconds, but we want to report seconds.
+ uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime) / 1000;
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween);
+ }
+ sLastCCEndTime = endCCTimeStamp;
+
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC);
+
+ PRTime delta = GetCollectionTimeDelta();
+
+ uint32_t cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1;
+ uint32_t minForgetSkippableTime = (sMinForgetSkippableTime == UINT32_MAX)
+ ? 0 : sMinForgetSkippableTime;
+
+ if (sPostGCEventsToConsole || gCCStats.mFile) {
+ nsCString mergeMsg;
+ if (aResults.mMergedZones) {
+ mergeMsg.AssignLiteral(" merged");
+ }
+
+ nsCString gcMsg;
+ if (aResults.mForcedGC) {
+ gcMsg.AssignLiteral(", forced a GC");
+ }
+
+ NS_NAMED_MULTILINE_LITERAL_STRING(kFmt,
+ u"CC(T+%.1f)[%s] max pause: %lums, total time: %lums, slices: %lu, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
+ u"ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu");
+ nsString msg;
+ msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog(),
+ gCCStats.mMaxSliceTime, gCCStats.mTotalSliceTime,
+ aResults.mNumSlices, gCCStats.mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sCCollectedWaitingForGC, sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
+ gcMsg.get(),
+ sForgetSkippableBeforeCC,
+ minForgetSkippableTime / PR_USEC_PER_MSEC,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+ (sTotalForgetSkippableTime / cleanups) /
+ PR_USEC_PER_MSEC,
+ sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+ gCCStats.mMaxSkippableDuration, sRemovedPurples));
+ if (sPostGCEventsToConsole) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+ if (gCCStats.mFile) {
+ fprintf(gCCStats.mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
+ }
+ }
+
+ if (sPostGCEventsToObserver) {
+ NS_NAMED_MULTILINE_LITERAL_STRING(kJSONFmt,
+ u"{ \"timestamp\": %llu, "
+ u"\"duration\": %lu, "
+ u"\"max_slice_pause\": %lu, "
+ u"\"total_slice_pause\": %lu, "
+ u"\"max_finish_gc_duration\": %lu, "
+ u"\"max_sync_skippable_duration\": %lu, "
+ 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\": %lu, "
+ u"\"max\": %lu, "
+ u"\"avg\": %lu, "
+ u"\"total\": %lu, "
+ u"\"removed\": %lu } "
+ u"}");
+ nsString json;
+
+ json.Adopt(nsTextFormatter::smprintf(kJSONFmt.get(), PR_Now(), ccNowDuration,
+ gCCStats.mMaxSliceTime,
+ gCCStats.mTotalSliceTime,
+ gCCStats.mMaxGCDuration,
+ gCCStats.mMaxSkippableDuration,
+ gCCStats.mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed,
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sCCollectedWaitingForGC,
+ sCCollectedZonesWaitingForGC,
+ sLikelyShortLivingObjectsNeedingGC,
+ aResults.mForcedGC,
+ sForgetSkippableBeforeCC,
+ minForgetSkippableTime / PR_USEC_PER_MSEC,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+ (sTotalForgetSkippableTime / cleanups) /
+ PR_USEC_PER_MSEC,
+ sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+ sRemovedPurples));
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "cycle-collection-statistics", json.get());
+ }
+ }
+
+ // Update global state to indicate we have just run a cycle collection.
+ sMinForgetSkippableTime = UINT32_MAX;
+ sMaxForgetSkippableTime = 0;
+ sTotalForgetSkippableTime = 0;
+ sRemovedPurples = 0;
+ sForgetSkippableBeforeCC = 0;
+ sNeedsFullCC = false;
+ sNeedsGCAfterCC = false;
+ gCCStats.Clear();
+}
+
+// static
+void
+InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ nsJSContext::KillInterSliceGCTimer();
+ nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
+ nsJSContext::IncrementalGC,
+ nsJSContext::NonShrinkingGC,
+ NS_INTERSLICE_GC_BUDGET);
+}
+
+// static
+void
+GCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ nsJSContext::KillGCTimer();
+ uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
+ nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
+ nsJSContext::IncrementalGC);
+}
+
+// static
+void
+ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ nsJSContext::KillShrinkingGCTimer();
+ sIsCompactingOnUserInactive = true;
+ nsJSContext::GarbageCollectNow(JS::gcreason::USER_INACTIVE,
+ nsJSContext::IncrementalGC,
+ nsJSContext::ShrinkingGC);
+}
+
+static bool
+ShouldTriggerCC(uint32_t aSuspected)
+{
+ return sNeedsFullCC ||
+ aSuspected > NS_CC_PURPLE_LIMIT ||
+ (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
+ TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
+}
+
+static void
+CCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ if (sDidShutdown) {
+ return;
+ }
+
+ static uint32_t ccDelay = NS_CC_DELAY;
+ if (sCCLockedOut) {
+ ccDelay = NS_CC_DELAY / 3;
+
+ PRTime now = PR_Now();
+ if (sCCLockedOutTime == 0) {
+ // Reset sCCTimerFireCount so that we run forgetSkippable
+ // often enough before CC. Because of reduced ccDelay
+ // forgetSkippable will be called just a few times.
+ // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
+ // forgetSkippable and CycleCollectNow eventually.
+ sCCTimerFireCount = 0;
+ sCCLockedOutTime = now;
+ return;
+ }
+ if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
+ return;
+ }
+ }
+
+ ++sCCTimerFireCount;
+
+ // During early timer fires, we only run forgetSkippable. During the first
+ // late timer fire, we decide if we are going to have a second and final
+ // late timer fire, where we may begin to run the CC. Should run at least one
+ // early timer fire to allow cleanup before the CC.
+ int32_t numEarlyTimerFires = std::max((int32_t)ccDelay / NS_CC_SKIPPABLE_DELAY - 2, 1);
+ bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires;
+ uint32_t suspected = nsCycleCollector_suspectedCount();
+ if (isLateTimerFire && ShouldTriggerCC(suspected)) {
+ if (sCCTimerFireCount == numEarlyTimerFires + 1) {
+ FireForgetSkippable(suspected, true);
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ // Our efforts to avoid a CC have failed, so we return to let the
+ // timer fire once more to trigger a CC.
+ return;
+ }
+ } else {
+ // We are in the final timer fire and still meet the conditions for
+ // triggering a CC. Let RunCycleCollectorSlice finish the current IGC, if
+ // any because that will allow us to include the GC time in the CC pause.
+ nsJSContext::RunCycleCollectorSlice();
+ }
+ } else if (((sPreviousSuspectedCount + 100) <= suspected) ||
+ (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS)) {
+ // Only do a forget skippable if there are more than a few new objects
+ // or we're doing the initial forget skippables.
+ FireForgetSkippable(suspected, false);
+ }
+
+ if (isLateTimerFire) {
+ ccDelay = NS_CC_DELAY;
+
+ // We have either just run the CC or decided we don't want to run the CC
+ // next time, so kill the timer.
+ sPreviousSuspectedCount = 0;
+ nsJSContext::KillCCTimer();
+ }
+}
+
+// static
+uint32_t
+nsJSContext::CleanupsSinceLastGC()
+{
+ return sCleanupsSinceLastGC;
+}
+
+// static
+void
+nsJSContext::LoadStart()
+{
+ sLoadingInProgress = true;
+ ++sPendingLoadCount;
+}
+
+// static
+void
+nsJSContext::LoadEnd()
+{
+ if (!sLoadingInProgress)
+ return;
+
+ // sPendingLoadCount is not a well managed load counter (and doesn't
+ // need to be), so make sure we don't make it wrap backwards here.
+ if (sPendingLoadCount > 0) {
+ --sPendingLoadCount;
+ return;
+ }
+
+ // Its probably a good idea to GC soon since we have finished loading.
+ sLoadingInProgress = false;
+ PokeGC(JS::gcreason::LOAD_END);
+}
+
+// Only trigger expensive timers when they have been checked a number of times.
+static bool
+ReadyToTriggerExpensiveCollectorTimer()
+{
+ bool ready = kPokesBetweenExpensiveCollectorTriggers < ++sExpensiveCollectorPokes;
+ if (ready) {
+ sExpensiveCollectorPokes = 0;
+ }
+ return ready;
+}
+
+
+// Check all of the various collector timers and see if they are waiting to fire.
+// For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger
+// the collection occasionally, because they are expensive. The incremental collector
+// timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so
+// always run their corresponding timer.
+
+// This does not check sFullGCTimer, as that's an even more expensive collection we run
+// on a long timer.
+
+// static
+void
+nsJSContext::RunNextCollectorTimer()
+{
+ if (sShuttingDown) {
+ return;
+ }
+
+ if (sGCTimer) {
+ if (ReadyToTriggerExpensiveCollectorTimer()) {
+ GCTimerFired(nullptr, reinterpret_cast<void *>(JS::gcreason::DOM_WINDOW_UTILS));
+ }
+ return;
+ }
+
+ if (sInterSliceGCTimer) {
+ InterSliceGCTimerFired(nullptr, nullptr);
+ return;
+ }
+
+ // Check the CC timers after the GC timers, because the CC timers won't do
+ // anything if a GC is in progress.
+ MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
+
+ if (sCCTimer) {
+ if (ReadyToTriggerExpensiveCollectorTimer()) {
+ CCTimerFired(nullptr, nullptr);
+ }
+ return;
+ }
+
+ if (sICCTimer) {
+ ICCTimerFired(nullptr, nullptr);
+ return;
+ }
+}
+
+// static
+void
+nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay)
+{
+ sNeedsFullGC = sNeedsFullGC || aReason != JS::gcreason::CC_WAITING;
+
+ if (sGCTimer || sInterSliceGCTimer || sShuttingDown) {
+ // There's already a timer for GC'ing, just return
+ return;
+ }
+
+ if (sCCTimer) {
+ // Make sure CC is called...
+ sNeedsFullCC = true;
+ // and GC after it.
+ sNeedsGCAfterCC = true;
+ return;
+ }
+
+ if (sICCTimer) {
+ // Make sure GC is called after the current CC completes.
+ // No need to set sNeedsFullCC because we are currently running a CC.
+ sNeedsGCAfterCC = true;
+ return;
+ }
+
+ CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
+
+ if (!sGCTimer) {
+ // Failed to create timer (probably because we're in XPCOM shutdown)
+ return;
+ }
+
+ static bool first = true;
+
+ sGCTimer->InitWithNamedFuncCallback(GCTimerFired,
+ reinterpret_cast<void *>(aReason),
+ aDelay
+ ? aDelay
+ : (first
+ ? NS_FIRST_GC_DELAY
+ : NS_GC_DELAY),
+ nsITimer::TYPE_ONE_SHOT,
+ "GCTimerFired");
+ first = false;
+}
+
+// static
+void
+nsJSContext::PokeShrinkingGC()
+{
+ if (sShrinkingGCTimer || sShuttingDown) {
+ return;
+ }
+
+ CallCreateInstance("@mozilla.org/timer;1", &sShrinkingGCTimer);
+
+ if (!sShrinkingGCTimer) {
+ // Failed to create timer (probably because we're in XPCOM shutdown)
+ return;
+ }
+
+ sShrinkingGCTimer->InitWithNamedFuncCallback(ShrinkingGCTimerFired, nullptr,
+ sCompactOnUserInactiveDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "ShrinkingGCTimerFired");
+}
+
+// static
+void
+nsJSContext::MaybePokeCC()
+{
+ if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
+ return;
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ sCCTimerFireCount = 0;
+ CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
+ if (!sCCTimer) {
+ return;
+ }
+ // We can kill some objects before running forgetSkippable.
+ nsCycleCollector_dispatchDeferredDeletion();
+
+ sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+ NS_CC_SKIPPABLE_DELAY,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "CCTimerFired");
+ }
+}
+
+//static
+void
+nsJSContext::KillGCTimer()
+{
+ if (sGCTimer) {
+ sGCTimer->Cancel();
+ NS_RELEASE(sGCTimer);
+ }
+}
+
+void
+nsJSContext::KillFullGCTimer()
+{
+ if (sFullGCTimer) {
+ sFullGCTimer->Cancel();
+ NS_RELEASE(sFullGCTimer);
+ }
+}
+
+void
+nsJSContext::KillInterSliceGCTimer()
+{
+ if (sInterSliceGCTimer) {
+ sInterSliceGCTimer->Cancel();
+ NS_RELEASE(sInterSliceGCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillShrinkingGCTimer()
+{
+ if (sShrinkingGCTimer) {
+ sShrinkingGCTimer->Cancel();
+ NS_RELEASE(sShrinkingGCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillCCTimer()
+{
+ sCCLockedOutTime = 0;
+ if (sCCTimer) {
+ sCCTimer->Cancel();
+ NS_RELEASE(sCCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillICCTimer()
+{
+ sCCLockedOutTime = 0;
+
+ if (sICCTimer) {
+ sICCTimer->Cancel();
+ NS_RELEASE(sICCTimer);
+ }
+}
+
+class NotifyGCEndRunnable : public Runnable
+{
+ nsString mMessage;
+
+public:
+ explicit NotifyGCEndRunnable(const nsString& aMessage) : mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+};
+
+NS_IMETHODIMP
+NotifyGCEndRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+
+ const char16_t oomMsg[3] = { '{', '}', 0 };
+ const char16_t *toSend = mMessage.get() ? mMessage.get() : oomMsg;
+ observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend);
+
+ return NS_OK;
+}
+
+static void
+DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
+{
+ NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
+
+ switch (aProgress) {
+ case JS::GC_CYCLE_BEGIN: {
+ // Prevent cycle collections and shrinking during incremental GC.
+ sCCLockedOut = true;
+ break;
+ }
+
+ case JS::GC_CYCLE_END: {
+ PRTime delta = GetCollectionTimeDelta();
+
+ if (sPostGCEventsToConsole) {
+ NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f)[%s] ");
+ nsString prefix, gcstats;
+ gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
+ prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
+ double(delta) / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog()));
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ if (!sShuttingDown) {
+ if (sPostGCEventsToObserver || Telemetry::CanRecordExtended()) {
+ nsString json;
+ json.Adopt(aDesc.formatJSON(aCx, PR_Now()));
+ RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
+ NS_DispatchToMainThread(notify);
+ }
+ }
+
+ sCCLockedOut = false;
+ sIsCompactingOnUserInactive = false;
+
+ // May need to kill the inter-slice GC timer
+ nsJSContext::KillInterSliceGCTimer();
+
+ sCCollectedWaitingForGC = 0;
+ sCCollectedZonesWaitingForGC = 0;
+ sLikelyShortLivingObjectsNeedingGC = 0;
+ sCleanupsSinceLastGC = 0;
+ sNeedsFullCC = true;
+ sHasRunGC = true;
+ nsJSContext::MaybePokeCC();
+
+ if (aDesc.isZone_) {
+ if (!sFullGCTimer && !sShuttingDown) {
+ CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
+ sFullGCTimer->InitWithNamedFuncCallback(FullGCTimerFired,
+ nullptr,
+ NS_FULL_GC_DELAY,
+ nsITimer::TYPE_ONE_SHOT,
+ "FullGCTimerFired");
+ }
+ } else {
+ nsJSContext::KillFullGCTimer();
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ break;
+ }
+
+ case JS::GC_SLICE_BEGIN:
+ break;
+
+ case JS::GC_SLICE_END:
+
+ // The GC has more work to do, so schedule another GC slice.
+ nsJSContext::KillInterSliceGCTimer();
+ if (!sShuttingDown) {
+ CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
+ sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
+ nullptr,
+ NS_INTERSLICE_GC_DELAY,
+ nsITimer::TYPE_ONE_SHOT,
+ "InterSliceGCTimerFired");
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ if (sPostGCEventsToConsole) {
+ NS_NAMED_LITERAL_STRING(kFmt, "[%s] ");
+ nsString prefix, gcstats;
+ gcstats.Adopt(aDesc.formatSliceMessage(aCx));
+ prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
+ ProcessNameForCollectorLog()));
+ 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()
+{
+ ++sLikelyShortLivingObjectsNeedingGC;
+}
+
+void
+mozilla::dom::StartupJSEnvironment()
+{
+ // initialize all our statics, so that we can restart XPCOM
+ sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr;
+ sCCLockedOut = false;
+ sCCLockedOutTime = 0;
+ sLastCCEndTime = TimeStamp();
+ sHasRunGC = false;
+ sPendingLoadCount = 0;
+ sLoadingInProgress = false;
+ sCCollectedWaitingForGC = 0;
+ sCCollectedZonesWaitingForGC = 0;
+ sLikelyShortLivingObjectsNeedingGC = 0;
+ sPostGCEventsToConsole = false;
+ sNeedsFullCC = false;
+ sNeedsFullGC = false;
+ sNeedsGCAfterCC = false;
+ gNameSpaceManager = nullptr;
+ sContext = nullptr;
+ sIsInitialized = false;
+ sDidShutdown = false;
+ sShuttingDown = false;
+ sContextCount = 0;
+ sSecurityManager = nullptr;
+ gCCStats.Init();
+ sExpensiveCollectorPokes = 0;
+}
+
+static void
+SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t highwatermark = Preferences::GetInt(aPrefName, 128);
+
+ JS_SetGCParameter(sContext, JSGC_MAX_MALLOC_BYTES,
+ highwatermark * 1024L * 1024L);
+}
+
+static void
+SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ uint32_t max = (pref <= 0 || pref >= 0x1000) ? -1 : (uint32_t)pref * 1024 * 1024;
+ JS_SetGCParameter(sContext, JSGC_MAX_BYTES, max);
+}
+
+static void
+SetMemoryGCModePrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool enableZoneGC = Preferences::GetBool("javascript.options.mem.gc_per_zone");
+ bool enableIncrementalGC = Preferences::GetBool("javascript.options.mem.gc_incremental");
+ JSGCMode mode;
+ if (enableIncrementalGC) {
+ mode = JSGC_MODE_INCREMENTAL;
+ } else if (enableZoneGC) {
+ mode = JSGC_MODE_ZONE;
+ } else {
+ mode = JSGC_MODE_GLOBAL;
+ }
+ JS_SetGCParameter(sContext, JSGC_MODE, mode);
+}
+
+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)
+ JS_SetGCParameter(sContext, JSGC_SLICE_TIME_BUDGET, pref);
+}
+
+static void
+SetMemoryGCCompactingPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_COMPACTING_ENABLED, pref);
+}
+
+static void
+SetMemoryGCPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref >= 0 && pref < 10000)
+ JS_SetGCParameter(sContext, (JSGCParamKey)(intptr_t)aClosure, pref);
+}
+
+static void
+SetMemoryGCDynamicHeapGrowthPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_DYNAMIC_HEAP_GROWTH, pref);
+}
+
+static void
+SetMemoryGCDynamicMarkSlicePrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_DYNAMIC_MARK_SLICE, pref);
+}
+
+static void
+SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_REFRESH_FRAME_SLICES_ENABLED, pref);
+}
+
+
+static void
+SetIncrementalCCPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ sIncrementalCC = pref;
+}
+
+static bool
+AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
+ const char16_t* aBegin,
+ const char16_t* aLimit,
+ size_t* aSize,
+ const uint8_t** aMemory,
+ intptr_t *aHandle)
+{
+ nsIPrincipal* principal =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
+ return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
+ aHandle);
+}
+
+static JS::AsmJSCacheResult
+AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
+ bool aInstalled,
+ const char16_t* aBegin,
+ const char16_t* aEnd,
+ size_t aSize,
+ uint8_t** aMemory,
+ intptr_t* aHandle)
+{
+ nsIPrincipal* principal =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
+ return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
+ aSize, aMemory, aHandle);
+}
+
+class AsyncTaskRunnable final : public Runnable
+{
+ ~AsyncTaskRunnable()
+ {
+ MOZ_ASSERT(!mTask);
+ }
+
+public:
+ explicit AsyncTaskRunnable(JS::AsyncTask* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(mTask);
+ }
+
+protected:
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sContext == mTask->user);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ mTask->finish(sContext);
+ mTask = nullptr; // mTask may delete itself
+
+ return NS_OK;
+ }
+
+private:
+ JS::AsyncTask* mTask;
+};
+
+static bool
+StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
+{
+ MOZ_ASSERT(aCx == sContext);
+ aTask->user = sContext;
+ return true;
+}
+
+static bool
+FinishAsyncTaskCallback(JS::AsyncTask* aTask)
+{
+ // AsyncTasks can finish during shutdown so cannot simply
+ // NS_DispatchToMainThread.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (!mainThread) {
+ return false;
+ }
+
+ RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(aTask);
+ MOZ_ALWAYS_SUCCEEDS(mainThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+ return true;
+}
+
+void
+nsJSContext::EnsureStatics()
+{
+ if (sIsInitialized) {
+ if (!nsContentUtils::XPConnect()) {
+ MOZ_CRASH();
+ }
+ return;
+ }
+
+ nsresult rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
+ &sSecurityManager);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+
+ sContext = danger::GetJSContext();
+ if (!sContext) {
+ MOZ_CRASH();
+ }
+
+ // Let's make sure that our main thread is the same as the xpcom main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sPrevGCSliceCallback = JS::SetGCSliceCallback(sContext, DOMGCSliceCallback);
+
+ // Set up the asm.js cache callbacks
+ static const JS::AsmJSCacheOps asmJSCacheOps = {
+ AsmJSCacheOpenEntryForRead,
+ asmjscache::CloseEntryForRead,
+ AsmJSCacheOpenEntryForWrite,
+ asmjscache::CloseEntryForWrite
+ };
+ JS::SetAsmJSCacheOps(sContext, &asmJSCacheOps);
+
+ JS::SetAsyncTaskCallbacks(sContext, StartAsyncTaskCallback, FinishAsyncTaskCallback);
+
+ // Set these global xpconnect options...
+ Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback,
+ "javascript.options.mem.high_water_mark");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback,
+ "javascript.options.mem.max");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
+ "javascript.options.mem.gc_per_zone");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
+ "javascript.options.mem.gc_incremental");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCSliceTimePrefChangedCallback,
+ "javascript.options.mem.gc_incremental_slice_ms");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCCompactingPrefChangedCallback,
+ "javascript.options.mem.gc_compacting");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_time_limit_ms",
+ (void *)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicMarkSlicePrefChangedCallback,
+ "javascript.options.mem.gc_dynamic_mark_slice");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback,
+ "javascript.options.mem.gc_refresh_frame_slices_enabled");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicHeapGrowthPrefChangedCallback,
+ "javascript.options.mem.gc_dynamic_heap_growth");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_low_frequency_heap_growth",
+ (void *)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_heap_growth_min",
+ (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_heap_growth_max",
+ (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_low_limit_mb",
+ (void *)JSGC_HIGH_FREQUENCY_LOW_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_high_limit_mb",
+ (void *)JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_allocation_threshold_mb",
+ (void *)JSGC_ALLOCATION_THRESHOLD);
+
+ Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
+ "dom.cycle_collector.incremental");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_min_empty_chunk_count",
+ (void *)JSGC_MIN_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_max_empty_chunk_count",
+ (void *)JSGC_MAX_EMPTY_CHUNK_COUNT);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ MOZ_CRASH();
+ }
+
+ Preferences::AddBoolVarCache(&sGCOnMemoryPressure,
+ "javascript.options.gc_on_memory_pressure",
+ true);
+
+ Preferences::AddBoolVarCache(&sCompactOnUserInactive,
+ "javascript.options.compact_on_user_inactive",
+ true);
+
+ Preferences::AddUintVarCache(&sCompactOnUserInactiveDelay,
+ "javascript.options.compact_on_user_inactive_delay",
+ NS_DEAULT_INACTIVE_GC_DELAY);
+
+ Preferences::AddBoolVarCache(&sPostGCEventsToConsole,
+ JS_OPTIONS_DOT_STR "mem.log");
+ Preferences::AddBoolVarCache(&sPostGCEventsToObserver,
+ JS_OPTIONS_DOT_STR "mem.notify");
+
+ 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);
+
+ sIsInitialized = true;
+}
+
+void
+nsJSContext::NotifyDidPaint()
+{
+ sDidPaintAfterPreviousICCSlice = true;
+ if (sICCTimer) {
+ static uint32_t sCount = 0;
+ // 16 here is the common value for refresh driver tick frequency.
+ static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
+ if (++sCount % kTicksPerSliceDelay != 0) {
+ // Don't trigger CC slice all the time after paint, but often still.
+ // The key point is to trigger it right after paint, especially when
+ // we're running RefreshDriver constantly.
+ return;
+ }
+
+ sICCTimer->Cancel();
+ ICCTimerFired(nullptr, nullptr);
+ if (sICCTimer) {
+ sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+ kICCIntersliceDelay,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "ICCTimerFired");
+ }
+ } else if (sCCTimer) {
+ static uint32_t sCount = 0;
+ static const uint32_t kTicksPerForgetSkippableDelay =
+ NS_CC_SKIPPABLE_DELAY / 16;
+ if (++sCount % kTicksPerForgetSkippableDelay != 0) {
+ // The comment above about triggering CC slice applies to forget skippable
+ // too.
+ return;
+ }
+
+ sCCTimer->Cancel();
+ CCTimerFired(nullptr, nullptr);
+ if (sCCTimer) {
+ sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+ NS_CC_SKIPPABLE_DELAY,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "CCTimerFired");
+ }
+ }
+}
+
+nsScriptNameSpaceManager*
+mozilla::dom::GetNameSpaceManager()
+{
+ if (sDidShutdown)
+ return nullptr;
+
+ if (!gNameSpaceManager) {
+ gNameSpaceManager = new nsScriptNameSpaceManager;
+ NS_ADDREF(gNameSpaceManager);
+
+ nsresult rv = gNameSpaceManager->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+
+ return gNameSpaceManager;
+}
+
+nsScriptNameSpaceManager*
+mozilla::dom::PeekNameSpaceManager()
+{
+ return gNameSpaceManager;
+}
+
+void
+mozilla::dom::ShutdownJSEnvironment()
+{
+ KillTimers();
+
+ NS_IF_RELEASE(gNameSpaceManager);
+
+ if (!sContextCount) {
+ // We're being shutdown, and there are no more contexts
+ // alive, release the security manager.
+ NS_IF_RELEASE(sSecurityManager);
+ }
+
+ sShuttingDown = true;
+ sDidShutdown = true;
+}
+
+// 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()
+{
+ if (mArgv) {
+ 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_SCRIPT_OBJECTS
+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::Enumerate(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;
+}