dom/base/nsJSEnvironment.cpp
author Cosmin Sabou <csabou@mozilla.com>
Sat, 19 Jan 2019 11:57:49 +0200
changeset 454597 f90bab5af97efa714181eea7fad45cf8cf14e3ea
parent 454462 e3a8a7245f627e6697056a18847f286c0a1d2bc9
parent 454520 5f4630838d46dd81dadb13220a4af0da9e23a619
child 454681 13840080fc24aef5ca298b38f2e0b714dc75d05f
permissions -rw-r--r--
Merge mozilla-inbound to mozilla-central. a=merge

/* -*- 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 "nsAtom.h"
#include "nsContentUtils.h"
#include "mozilla/EventDispatcher.h"
#include "nsIContent.h"
#include "nsCycleCollector.h"
#include "nsXPCOMCIDInternal.h"
#include "nsIXULRuntime.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/PropertySpec.h"
#include "js/SliceBudget.h"
#include "js/Wrapper.h"
#include "nsIArray.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "WrapperFactory.h"
#include "nsGlobalWindow.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MainThreadIdlePeriod.h"
#include "mozilla/StaticPrefs.h"
#include "mozilla/StaticPtr.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/ScriptSettings.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/SystemGroup.h"
#include "nsRefreshDriver.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/ContentEvents.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "nsCycleCollectionNoteRootCallback.h"
#include "GeckoProfiler.h"
#include "mozilla/IdleTaskRunner.h"
#include "nsIDocShell.h"
#include "nsIPresShell.h"
#include "nsViewManager.h"
#include "mozilla/EventStateManager.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

// Maximum amount of time that should elapse between incremental GC slices
#define NS_INTERSLICE_GC_DELAY 100  // 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

// In case the cycle collector isn't run at all, we don't want
// forget skippables to run too often. So limit the forget skippable cycle to
// start at earliest 2000 ms after the end of the previous cycle.
#define NS_TIME_BETWEEN_FORGET_SKIPPABLE_CYCLES 2000  // ms

// ForgetSkippable is usually fast, so we can use small budgets.
// This isn't a real budget but a hint to IdleTaskRunner whether there
// is enough time to call ForgetSkippable.
static const int64_t kForgetSkippableSliceDuration = 2;

// Maximum amount of time that should elapse between incremental CC slices
static const int64_t kICCIntersliceDelay = 64;  // ms

// Time budget for an incremental CC slice when using timer to run it.
static const int64_t kICCSliceBudget = 3;  // ms
// Minimum budget for an incremental CC slice when using idle time to run it.
static const int64_t kIdleICCSliceBudget = 2;  // 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 StaticRefPtr<IdleTaskRunner> sCCRunner;
static StaticRefPtr<IdleTaskRunner> sICCRunner;
static nsITimer* sFullGCTimer;
static StaticRefPtr<IdleTaskRunner> sInterSliceGCRunner;

static TimeStamp sLastCCEndTime;

static TimeStamp sLastForgetSkippableCycleEndTime;

static bool sCCLockedOut;
static PRTime sCCLockedOutTime;

static JS::GCSliceCallback sPrevGCSliceCallback;

static bool sHasRunGC;

static uint32_t sCCollectedWaitingForGC;
static uint32_t sCCollectedZonesWaitingForGC;
static uint32_t sLikelyShortLivingObjectsNeedingGC;
static int32_t sCCRunnerFireCount = 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 int32_t sActiveIntersliceGCBudget = 5;  // ms;

static PRTime sFirstCollectionTime;

static bool sIsInitialized;
static bool sDidShutdown;
static bool sShuttingDown;

// 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 sIsCompactingOnUserInactive = false;

static TimeDuration sGCUnnotifiedTotalTime;

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 stackObj and stackGlobal are _not_ wrapped into the
// compartment of exceptionValue.
void FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
                                        JS::HandleValue exceptionValue,
                                        JS::MutableHandleObject stackObj,
                                        JS::MutableHandleObject stackGlobal) {
  stackObj.set(nullptr);
  stackGlobal.set(nullptr);

  if (!exceptionValue.isObject()) {
    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::RootedObject 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) {
      return;
    }
  }

  nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
  if (!stack) {
    return;
  }
  JS::RootedValue 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 PRTime GetCollectionTimeDelta() {
  PRTime now = PR_Now();
  if (sFirstCollectionTime) {
    return now - sFirstCollectionTime;
  }
  sFirstCollectionTime = now;
  return 0;
}

static void KillTimers() {
  nsJSContext::KillGCTimer();
  nsJSContext::KillShrinkingGCTimer();
  nsJSContext::KillCCRunner();
  nsJSContext::KillICCRunner();
  nsJSContext::KillFullGCTimer();
  nsJSContext::KillInterSliceGCRunner();
}

// 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 (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
      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 (StaticPrefs::javascript_options_compact_on_user_inactive()) {
      nsJSContext::PokeShrinkingGC();
    }
  } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
    nsJSContext::KillShrinkingGCTimer();
    if (sIsCompactingOnUserInactive) {
      AutoJSAPI jsapi;
      jsapi.Init();
      JS::AbortIncrementalGC(jsapi.cx());
    }
    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();

    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),
                                  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)
      : mozilla::Runnable("ScriptErrorEvent"),
        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();

      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(
          nsGlobalWindowInner::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);
      JS::Rooted<JSObject*> stackGlobal(rootingCx);
      xpc::FindExceptionStackForConsoleReport(win, mError, &stack,
                                              &stackGlobal);
      mReport->LogToConsoleWithStack(stack, stackGlobal,
                                     JS::ExceptionTimeWarpTarget(mError));
    }

    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.
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;
  }

  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 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();

  mIsInitialized = false;
  mProcessingScriptTag = false;
  HoldJSObjects(this);
}

nsJSContext::~nsJSContext() {
  mGlobalObjectRef = nullptr;

  Destroy();
}

void nsJSContext::Destroy() {
  if (mGCOnDestruction) {
    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->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_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::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
          JSAutoRealm ar(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) {
  MOZ_ASSERT(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);
      JSAutoRealm ar(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: {
      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);

  // 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) {
  AUTO_PROFILER_LABEL_DYNAMIC_CSTR("nsJSContext::GarbageCollectNow", GCCC,
                                   JS::gcreason::ExplainReason(aReason));

  MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);

  KillGCTimer();

  // 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 (sCCLockedOut && aIncremental == IncrementalGC) {
    // We're in the middle of incremental GC. Do another slice.
    JS::PrepareForIncrementalGC(cx);
    JS::IncrementalGCSlice(cx, aReason, aSliceMillis);
    return;
  }

  JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;

  if (aIncremental == NonIncrementalGC ||
      aReason == JS::gcreason::FULL_GC_TIMER) {
    sNeedsFullGC = true;
  }

  if (sNeedsFullGC) {
    JS::PrepareForFullGC(cx);
  } else {
    CycleCollectedJSRuntime::Get()->PrepareWaitingZonesForGC();
  }

  if (aIncremental == IncrementalGC) {
    JS::StartIncrementalGC(cx, gckind, aReason, aSliceMillis);
  } else {
    JS::NonIncrementalGC(cx, gckind, aReason);
  }
}

static void FinishAnyIncrementalGC() {
  AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);

  if (sCCLockedOut) {
    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);
  }
}

static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless,
                                TimeStamp aDeadline) {
  AUTO_PROFILER_TRACING(
      "CC", aDeadline.IsNull() ? "ForgetSkippable" : "IdleForgetSkippable",
      GCCC);
  PRTime startTime = PR_Now();
  TimeStamp startTimeStamp = TimeStamp::Now();

  static uint32_t sForgetSkippableCounter = 0;
  static TimeStamp sForgetSkippableFrequencyStartTime;
  static TimeStamp sLastForgetSkippableEndTime;
  static const TimeDuration minute = TimeDuration::FromSeconds(60.0f);

  if (sForgetSkippableFrequencyStartTime.IsNull()) {
    sForgetSkippableFrequencyStartTime = startTimeStamp;
  } else if (startTimeStamp - sForgetSkippableFrequencyStartTime > minute) {
    TimeStamp startPlusMinute = sForgetSkippableFrequencyStartTime + minute;

    // If we had forget skippables only at the beginning of the interval, we
    // still want to use the whole time, minute or more, for frequency
    // calculation. sLastForgetSkippableEndTime is needed if forget skippable
    // takes enough time to push the interval to be over a minute.
    TimeStamp endPoint = startPlusMinute > sLastForgetSkippableEndTime
                             ? startPlusMinute
                             : sLastForgetSkippableEndTime;

    // Duration in minutes.
    double duration =
        (endPoint - sForgetSkippableFrequencyStartTime).ToSeconds() / 60;
    uint32_t frequencyPerMinute = uint32_t(sForgetSkippableCounter / duration);
    Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
                          frequencyPerMinute);
    sForgetSkippableCounter = 0;
    sForgetSkippableFrequencyStartTime = startTimeStamp;
  }
  ++sForgetSkippableCounter;

  FinishAnyIncrementalGC();
  bool earlyForgetSkippable =
      sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS;

  int64_t budgetMs =
      aDeadline.IsNull()
          ? kForgetSkippableSliceDuration
          : int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
  js::SliceBudget budget = js::SliceBudget(js::TimeBudget(budgetMs));
  nsCycleCollector_forgetSkippable(budget, 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;

  TimeStamp now = TimeStamp::Now();
  sLastForgetSkippableEndTime = now;

  TimeDuration duration = now - startTimeStamp;
  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);
  }
}

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),
        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;
  }

  void PrepareForCycleCollectionSlice(TimeStamp aDeadline = TimeStamp());

  void FinishCycleCollectionSlice() {
    if (mBeginSliceTime.IsNull()) {
      // We already called this method from EndCycleCollectionCallback for this
      // slice.
      return;
    }

    mEndSliceTime = TimeStamp::Now();
    TimeDuration duration = mEndSliceTime - mBeginSliceTime;

    if (duration.ToSeconds()) {
      TimeDuration idleDuration;
      if (!mIdleDeadline.IsNull()) {
        if (mIdleDeadline < mEndSliceTime) {
          // This slice overflowed the idle period.
          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);
    }

    uint32_t sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
    mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
    mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
    mTotalSliceTime += sliceTime;
    mBeginSliceTime = TimeStamp();
  }

  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;

  // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
  FILE* mFile;

  // In case CC slice was triggered during idle time, set to the end of the idle
  // period.
  TimeStamp mIdleDeadline;
};

CycleCollectorStats gCCStats;

void CycleCollectorStats::PrepareForCycleCollectionSlice(TimeStamp aDeadline) {
  mBeginSliceTime = TimeStamp::Now();
  mIdleDeadline = aDeadline;

  // 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);
  }
}

void CycleCollectorStats::RunForgetSkippable() {
  // Run forgetSkippable synchronously to reduce the size of the CC graph. This
  // is particularly useful if we recently finished a GC.
  TimeStamp beginForgetSkippable = TimeStamp::Now();
  bool ranSyncForgetSkippable = false;
  while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
    FireForgetSkippable(nsCycleCollector_suspectedCount(), false, TimeStamp());
    ranSyncForgetSkippable = true;
  }

  if (ranSyncForgetSkippable) {
    mMaxSkippableDuration =
        std::max(mMaxSkippableDuration, TimeUntilNow(beginForgetSkippable));
    mRanSyncForgetSkippable = true;
  }
}

// static
void nsJSContext::CycleCollectNow(nsICycleCollectorListener* aListener) {
  if (!NS_IsMainThread()) {
    return;
  }

  AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);

  gCCStats.PrepareForCycleCollectionSlice(TimeStamp());
  nsCycleCollector_collect(aListener);
  gCCStats.FinishCycleCollectionSlice();
}

// static
void nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline) {
  if (!NS_IsMainThread()) {
    return;
  }

  AUTO_PROFILER_TRACING("CC", aDeadline.IsNull() ? "CCSlice" : "IdleCCSlice",
                        GCCC);

  AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorSlice", GCCC);

  gCCStats.PrepareForCycleCollectionSlice(aDeadline);

  // Decide how long we want to budget for this slice. By default,
  // use an unlimited budget.
  js::SliceBudget budget = js::SliceBudget::unlimited();

  if (sIncrementalCC) {
    int64_t baseBudget = kICCSliceBudget;
    if (!aDeadline.IsNull()) {
      baseBudget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
    }

    if (gCCStats.mBeginTime.IsNull()) {
      // If no CC is in progress, use the standard slice time.
      budget = js::SliceBudget(js::TimeBudget(baseBudget));
    } else {
      TimeStamp now = TimeStamp::Now();

      // Only run a limited slice if we're within the max running time.
      uint32_t runningTime = TimeBetween(gCCStats.mBeginTime, now);
      if (runningTime < kMaxICCDuration) {
        const float maxSlice = MainThreadIdlePeriod::GetLongIdlePeriod();

        // Try to make up for a delay in running this slice.
        float sliceDelayMultiplier = TimeBetween(gCCStats.mEndSliceTime, now) /
                                     (float)kICCIntersliceDelay;
        float delaySliceBudget =
            std::min(baseBudget * sliceDelayMultiplier, maxSlice);

        // Increase slice budgets up to |maxSlice| as we approach
        // half way through the ICC, to avoid large sync CCs.
        float percentToHalfDone =
            std::min(2.0f * runningTime / kMaxICCDuration, 1.0f);
        float laterSliceBudget = maxSlice * percentToHalfDone;

        budget = js::SliceBudget(js::TimeBudget(
            std::max({delaySliceBudget, laterSliceBudget, (float)baseBudget})));
      }
    }
  }

  nsCycleCollector_collectSlice(
      budget,
      aDeadline.IsNull() ||
          (aDeadline - TimeStamp::Now()).ToMilliseconds() < kICCSliceBudget);

  gCCStats.FinishCycleCollectionSlice();
}

// static
void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
  if (!NS_IsMainThread()) {
    return;
  }

  AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);

  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 bool ICCRunnerFired(TimeStamp aDeadline) {
  if (sDidShutdown) {
    return false;
  }

  // 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 false;
    }
    if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
      return false;
    }
  }

  nsJSContext::RunCycleCollectorSlice(aDeadline);
  return true;
}

// static
void nsJSContext::BeginCycleCollectionCallback() {
  MOZ_ASSERT(NS_IsMainThread());

  gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull()
                            ? TimeStamp::Now()
                            : gCCStats.mBeginSliceTime;
  gCCStats.mSuspected = nsCycleCollector_suspectedCount();

  KillCCRunner();

  gCCStats.RunForgetSkippable();

  MOZ_ASSERT(!sICCRunner,
             "Tried to create a new ICC timer when one already existed.");

  if (sShuttingDown) {
    return;
  }

  // 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.
  sICCRunner = IdleTaskRunner::Create(
      ICCRunnerFired, "BeginCycleCollectionCallback::ICCRunnerFired",
      kICCIntersliceDelay, kIdleICCSliceBudget, true,
      [] { return sShuttingDown; }, TaskCategory::GarbageCollection);
}

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::KillICCRunner();

  // 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, nullptr,
           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 (StaticPrefs::javascript_options_mem_log() || gCCStats.mFile) {
    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: %lums, total time: %lums, 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: %lu ms, max: %lu ms, avg: "
        u"%lu ms, total: %lu ms, max sync: %lu ms, removed: %lu";
    nsString msg;
    nsTextFormatter::ssprintf(
        msg, kFmt, double(delta) / PR_USEC_PER_SEC,
        ProcessNameForCollectorLog(), getpid(), 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 (StaticPrefs::javascript_options_mem_log()) {
      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 (StaticPrefs::javascript_options_mem_notify()) {
    const char16_t* 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;
    nsTextFormatter::ssprintf(
        json, kJSONFmt, 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
bool InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData) {
  nsJSContext::KillInterSliceGCRunner();
  MOZ_ASSERT(sActiveIntersliceGCBudget > 0);
  // We use longer budgets when the CC has been locked out but the CC has tried
  // to run since that means we may have significant amount garbage to collect
  // and better to GC in several longer slices than in a very long one.
  int64_t budget =
      aDeadline.IsNull()
          ? int64_t(sActiveIntersliceGCBudget * 2)
          : int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
  if (sCCLockedOut && sCCLockedOutTime) {
    int64_t lockedTime = PR_Now() - sCCLockedOutTime;
    int32_t maxSliceGCBudget = sActiveIntersliceGCBudget * 10;
    double percentOfLockedTime =
        std::min((double)lockedTime / NS_MAX_CC_LOCKEDOUT_TIME, 1.0);
    budget = static_cast<int64_t>(
        std::max((double)budget, percentOfLockedTime * maxSliceGCBudget));
  }

  TimeStamp startTimeStamp = TimeStamp::Now();
  TimeDuration duration = sGCUnnotifiedTotalTime;
  uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
  nsJSContext::GarbageCollectNow(
      aData ? static_cast<JS::gcreason::Reason>(reason)
            : JS::gcreason::INTER_SLICE_GC,
      nsJSContext::IncrementalGC, nsJSContext::NonShrinkingGC, budget);

  sGCUnnotifiedTotalTime = TimeDuration();
  TimeStamp now = TimeStamp::Now();
  TimeDuration sliceDuration = now - startTimeStamp;
  duration += sliceDuration;
  if (duration.ToSeconds()) {
    TimeDuration idleDuration;
    if (!aDeadline.IsNull()) {
      if (aDeadline < now) {
        // This slice overflowed the idle period.
        idleDuration = aDeadline - startTimeStamp;
      } else {
        // Note, we don't want to use duration here, since it may contain
        // data also from JS engine triggered GC slices.
        idleDuration = sliceDuration;
      }
    }

    uint32_t percent =
        uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
    Telemetry::Accumulate(Telemetry::GC_SLICE_DURING_IDLE, percent);
  }
  return true;
}

// static
void GCTimerFired(nsITimer* aTimer, void* aClosure) {
  nsJSContext::KillGCTimer();
  nsJSContext::KillInterSliceGCRunner();
  if (sShuttingDown) {
    return;
  }

  // Now start the actual GC after initial timer has fired.
  sInterSliceGCRunner = IdleTaskRunner::Create(
      [aClosure](TimeStamp aDeadline) {
        return InterSliceGCRunnerFired(aDeadline, aClosure);
      },
      "GCTimerFired::InterSliceGCRunnerFired", NS_INTERSLICE_GC_DELAY,
      sActiveIntersliceGCBudget, false, [] { return sShuttingDown; },
      TaskCategory::GarbageCollection);
}

// 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 bool CCRunnerFired(TimeStamp aDeadline) {
  if (sDidShutdown) {
    return false;
  }

  static uint32_t ccDelay = NS_CC_DELAY;
  if (sCCLockedOut) {
    ccDelay = NS_CC_DELAY / 3;

    PRTime now = PR_Now();
    if (sCCLockedOutTime == 0) {
      // Reset sCCRunnerFireCount 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.
      sCCRunnerFireCount = 0;
      sCCLockedOutTime = now;
      return false;
    }
    if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
      return false;
    }
  }

  ++sCCRunnerFireCount;

  bool didDoWork = false;

  // 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 = sCCRunnerFireCount > numEarlyTimerFires;
  uint32_t suspected = nsCycleCollector_suspectedCount();
  if (isLateTimerFire && ShouldTriggerCC(suspected)) {
    if (sCCRunnerFireCount == numEarlyTimerFires + 1) {
      FireForgetSkippable(suspected, true, aDeadline);
      didDoWork = 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.

        if (!aDeadline.IsNull() && TimeStamp::Now() < aDeadline) {
          // Clear content unbinder before the first CC slice.
          Element::ClearContentUnbinder();

          if (TimeStamp::Now() < aDeadline) {
            // And trigger deferred deletion too.
            nsCycleCollector_doDeferredDeletion();
          }
        }
        return didDoWork;
      }
    } 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(aDeadline);
      didDoWork = true;
    }
  } 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, aDeadline);
    didDoWork = true;
  }

  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::KillCCRunner();

    if (!didDoWork) {
      sLastForgetSkippableCycleEndTime = TimeStamp::Now();
    }
  }

  return didDoWork;
}

// static
uint32_t nsJSContext::CleanupsSinceLastGC() { return sCleanupsSinceLastGC; }

// Check all of the various collector timers/runners and see if they are waiting
// to fire. This does not check sFullGCTimer, as that's a more expensive
// collection we run on a long timer.

// static
void nsJSContext::RunNextCollectorTimer(JS::gcreason::Reason aReason,
                                        mozilla::TimeStamp aDeadline) {
  if (sShuttingDown) {
    return;
  }

  if (sGCTimer) {
    GCTimerFired(nullptr, reinterpret_cast<void*>(aReason));
    return;
  }

  nsCOMPtr<nsIRunnable> runnable;
  if (sInterSliceGCRunner) {
    sInterSliceGCRunner->SetDeadline(aDeadline);
    runnable = sInterSliceGCRunner;
  } else {
    // 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 (sCCRunner) {
    sCCRunner->SetDeadline(aDeadline);
    runnable = sCCRunner;
  }

  if (sICCRunner) {
    sICCRunner->SetDeadline(aDeadline);
    runnable = sICCRunner;
  }

  if (runnable) {
    runnable->Run();
  }
}

// static
void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
                                             JS::gcreason::Reason aReason) {
  if (!aDocShell || !XRE_IsContentProcess()) {
    return;
  }

  nsCOMPtr<nsIDocShellTreeItem> root;
  aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
  if (root == aDocShell) {
    // We don't want to run collectors when loading the top level page.
    return;
  }

  Document* rootDocument = root->GetDocument();
  if (!rootDocument ||
      rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
      rootDocument->IsInBackgroundWindow()) {
    return;
  }

  nsIPresShell* presShell = rootDocument->GetShell();
  if (!presShell) {
    return;
  }

  nsViewManager* vm = presShell->GetViewManager();
  if (!vm) {
    return;
  }

  // GetLastUserEventTime returns microseconds.
  uint32_t lastEventTime = 0;
  vm->GetLastUserEventTime(lastEventTime);
  uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
  // Only try to trigger collectors more often if user hasn't interacted with
  // the page for awhile.
  if ((currentTime - lastEventTime) >
      (NS_USER_INTERACTION_INTERVAL * PR_USEC_PER_MSEC)) {
    Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
    // Try to not delay the next RefreshDriver tick, so give a reasonable
    // deadline for collectors.
    if (next.isSome()) {
      nsJSContext::RunNextCollectorTimer(aReason, next.value());
    }
  }
}

// static
void nsJSContext::PokeGC(JS::gcreason::Reason aReason, JSObject* aObj,
                         int aDelay) {
  if (sShuttingDown) {
    return;
  }

  if (aObj) {
    JS::Zone* zone = JS::GetObjectZone(aObj);
    CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
  } else if (aReason != JS::gcreason::CC_WAITING) {
    sNeedsFullGC = true;
  }

  if (sGCTimer || sInterSliceGCRunner) {
    // There's already a timer for GC'ing, just return
    return;
  }

  if (sCCRunner) {
    // Make sure CC is called...
    sNeedsFullCC = true;
    // and GC after it.
    sNeedsGCAfterCC = true;
    return;
  }

  if (sICCRunner) {
    // 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;
  }

  static bool first = true;

  NS_NewTimerWithFuncCallback(
      &sGCTimer, GCTimerFired, reinterpret_cast<void*>(aReason),
      aDelay ? aDelay : (first ? NS_FIRST_GC_DELAY : NS_GC_DELAY),
      nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "GCTimerFired",
      SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));

  first = false;
}

// static
void nsJSContext::PokeShrinkingGC() {
  if (sShrinkingGCTimer || sShuttingDown) {
    return;
  }

  NS_NewTimerWithFuncCallback(
      &sShrinkingGCTimer, ShrinkingGCTimerFired, nullptr,
      StaticPrefs::javascript_options_compact_on_user_inactive_delay(),
      nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "ShrinkingGCTimerFired",
      SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
}

// static
void nsJSContext::MaybePokeCC() {
  if (sCCRunner || sICCRunner || !sHasRunGC || sShuttingDown) {
    return;
  }

  uint32_t sinceLastCCEnd = TimeUntilNow(sLastCCEndTime);
  if (sinceLastCCEnd && sinceLastCCEnd < NS_CC_DELAY) {
    return;
  }

  // If GC hasn't run recently and forget skippable only cycle was run,
  // don't start a new cycle too soon.
  if (sCleanupsSinceLastGC > NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
    uint32_t sinceLastForgetSkippableCycle =
        TimeUntilNow(sLastForgetSkippableCycleEndTime);
    if (sinceLastForgetSkippableCycle &&
        sinceLastForgetSkippableCycle <
            NS_TIME_BETWEEN_FORGET_SKIPPABLE_CYCLES) {
      return;
    }
  }

  if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
    sCCRunnerFireCount = 0;

    // We can kill some objects before running forgetSkippable.
    nsCycleCollector_dispatchDeferredDeletion();

    sCCRunner = IdleTaskRunner::Create(
        CCRunnerFired, "MaybePokeCC::CCRunnerFired", NS_CC_SKIPPABLE_DELAY,
        kForgetSkippableSliceDuration, true, [] { return sShuttingDown; },
        TaskCategory::GarbageCollection);
  }
}

// static
void nsJSContext::KillGCTimer() {
  if (sGCTimer) {
    sGCTimer->Cancel();
    NS_RELEASE(sGCTimer);
  }
}

void nsJSContext::KillFullGCTimer() {
  if (sFullGCTimer) {
    sFullGCTimer->Cancel();
    NS_RELEASE(sFullGCTimer);
  }
}

void nsJSContext::KillInterSliceGCRunner() {
  if (sInterSliceGCRunner) {
    sInterSliceGCRunner->Cancel();
    sInterSliceGCRunner = nullptr;
  }
}

// static
void nsJSContext::KillShrinkingGCTimer() {
  if (sShrinkingGCTimer) {
    sShrinkingGCTimer->Cancel();
    NS_RELEASE(sShrinkingGCTimer);
  }
}

// static
void nsJSContext::KillCCRunner() {
  sCCLockedOutTime = 0;
  if (sCCRunner) {
    sCCRunner->Cancel();
    sCCRunner = nullptr;
  }
}

// static
void nsJSContext::KillICCRunner() {
  sCCLockedOutTime = 0;

  if (sICCRunner) {
    sICCRunner->Cancel();
    sICCRunner = nullptr;
  }
}

class NotifyGCEndRunnable : public Runnable {
  nsString mMessage;

 public:
  explicit NotifyGCEndRunnable(nsString&& aMessage)
      : mozilla::Runnable("NotifyGCEndRunnable"),
        mMessage(std::move(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 (StaticPrefs::javascript_options_mem_log()) {
        nsString gcstats;
        gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
        nsAutoString prefix;
        nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
                                  double(delta) / PR_USEC_PER_SEC,
                                  ProcessNameForCollectorLog(), getpid());
        nsString msg = prefix + gcstats;
        nsCOMPtr<nsIConsoleService> cs =
            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
        if (cs) {
          cs->LogStringMessage(msg.get());
        }
      }

      if (!sShuttingDown) {
        if (StaticPrefs::javascript_options_mem_notify() ||
            Telemetry::CanRecordExtended()) {
          nsString json;
          json.Adopt(aDesc.formatJSONTelemetry(aCx, PR_Now()));
          RefPtr<NotifyGCEndRunnable> notify =
              new NotifyGCEndRunnable(std::move(json));
          SystemGroup::Dispatch(TaskCategory::GarbageCollection,
                                notify.forget());
        }
      }

      sCCLockedOut = false;
      sIsCompactingOnUserInactive = false;

      // May need to kill the inter-slice GC runner
      nsJSContext::KillInterSliceGCRunner();

      sCCollectedWaitingForGC = 0;
      sCCollectedZonesWaitingForGC = 0;
      sLikelyShortLivingObjectsNeedingGC = 0;
      sCleanupsSinceLastGC = 0;
      sNeedsFullCC = true;
      sHasRunGC = true;
      nsJSContext::MaybePokeCC();

      if (aDesc.isZone_) {
        if (!sFullGCTimer && !sShuttingDown) {
          NS_NewTimerWithFuncCallback(
              &sFullGCTimer, FullGCTimerFired, nullptr, NS_FULL_GC_DELAY,
              nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "FullGCTimerFired",
              SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
        }
      } else {
        nsJSContext::KillFullGCTimer();
      }

      if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
        nsCycleCollector_dispatchDeferredDeletion();
      }

      if (!aDesc.isZone_) {
        sNeedsFullGC = false;
      }

      break;
    }

    case JS::GC_SLICE_BEGIN:
      break;

    case JS::GC_SLICE_END:
      sGCUnnotifiedTotalTime +=
          aDesc.lastSliceEnd(aCx) - aDesc.lastSliceStart(aCx);

      // Schedule another GC slice if the GC has more work to do.
      nsJSContext::KillInterSliceGCRunner();
      if (!sShuttingDown && !aDesc.isComplete_) {
        sInterSliceGCRunner = IdleTaskRunner::Create(
            [](TimeStamp aDeadline) {
              return InterSliceGCRunnerFired(aDeadline, nullptr);
            },
            "DOMGCSliceCallback::InterSliceGCRunnerFired",
            NS_INTERSLICE_GC_DELAY, sActiveIntersliceGCBudget, false,
            [] { return sShuttingDown; }, TaskCategory::GarbageCollection);
      }

      if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
        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() {
  ++sLikelyShortLivingObjectsNeedingGC;
}

void mozilla::dom::StartupJSEnvironment() {
  // initialize all our statics, so that we can restart XPCOM
  sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
  sCCLockedOut = false;
  sCCLockedOutTime = 0;
  sLastCCEndTime = TimeStamp();
  sLastForgetSkippableCycleEndTime = TimeStamp();
  sHasRunGC = false;
  sCCollectedWaitingForGC = 0;
  sCCollectedZonesWaitingForGC = 0;
  sLikelyShortLivingObjectsNeedingGC = 0;
  sNeedsFullCC = false;
  sNeedsFullGC = true;
  sNeedsGCAfterCC = false;
  sIsInitialized = false;
  sDidShutdown = false;
  sShuttingDown = false;
  gCCStats.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 SetMemoryNurseryMaxPrefChangedCallback(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;
  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 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;
  }

  SetGCParameter(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) {
    sActiveIntersliceGCBudget = pref;
    SetGCParameter(JSGC_SLICE_TIME_BUDGET, pref);
  } else {
    ResetGCParameter(JSGC_SLICE_TIME_BUDGET);
  }
}

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::GetRealmPrincipals(js::GetNonCCWObjectRealm(aGlobal)));
  return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
                                      aHandle);
}

static JS::AsmJSCacheResult AsmJSCacheOpenEntryForWrite(
    JS::Handle<JSObject*> aGlobal, const char16_t* aBegin, const char16_t* aEnd,
    size_t aSize, uint8_t** aMemory, intptr_t* aHandle) {
  nsIPrincipal* principal = nsJSPrincipals::get(
      JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aGlobal)));
  return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
                                       aHandle);
}

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 = GetMainThreadEventTarget();
  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::HandleObject aObj,
                          JS::MimeType aMimeType,
                          JS::StreamConsumer* aConsumer) {
  return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
                                       nullptr);
}

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);

  // Set up the asm.js cache callbacks
  static const JS::AsmJSCacheOps asmJSCacheOps = {
      AsmJSCacheOpenEntryForRead, asmjscache::CloseEntryForRead,
      AsmJSCacheOpenEntryForWrite, asmjscache::CloseEntryForWrite};
  JS::SetAsmJSCacheOps(jsapi.cx(), &asmJSCacheOps);

  JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
  JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
                                FetchUtil::ReportJSStreamError);

  // Set these global xpconnect options...
  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
                                       "javascript.options.mem.high_water_mark",
                                       (void*)JSGC_MAX_MALLOC_BYTES);

  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
                                       "javascript.options.mem.max",
                                       (void*)JSGC_MAX_BYTES);
  Preferences::RegisterCallbackAndCall(SetMemoryNurseryMaxPrefChangedCallback,
                                       "javascript.options.mem.nursery.max_kb",
                                       (void*)JSGC_MAX_NURSERY_BYTES);

  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(SetMemoryPrefChangedCallbackBool,
                                       "javascript.options.mem.gc_compacting",
                                       (void*)JSGC_COMPACTING_ENABLED);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_high_frequency_time_limit_ms",
      (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackBool,
      "javascript.options.mem.gc_dynamic_mark_slice",
      (void*)JSGC_DYNAMIC_MARK_SLICE);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackBool,
      "javascript.options.mem.gc_dynamic_heap_growth",
      (void*)JSGC_DYNAMIC_HEAP_GROWTH);

  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_heap_growth_min",
      (void*)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_high_frequency_heap_growth_max",
      (void*)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_high_frequency_low_limit_mb",
      (void*)JSGC_HIGH_FREQUENCY_LOW_LIMIT);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_high_frequency_high_limit_mb",
      (void*)JSGC_HIGH_FREQUENCY_HIGH_LIMIT);

  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_allocation_threshold_mb",
      (void*)JSGC_ALLOCATION_THRESHOLD);
  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_allocation_threshold_factor",
      (void*)JSGC_ALLOCATION_THRESHOLD_FACTOR);
  Preferences::RegisterCallbackAndCall(
      SetMemoryPrefChangedCallbackInt,
      "javascript.options.mem.gc_allocation_threshold_factor_avoid_interrupt",
      (void*)JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT);

  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);

  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);

  sIsInitialized = true;
}

void mozilla::dom::ShutdownJSEnvironment() {
  KillTimers();

  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_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;
}