forked from mirrors/gecko-dev
Backed out changeset 52a86c60c866 (bug 1578623) Backed out changeset e4fddd3d24b4 (bug 1578623) Backed out changeset 65a7c4daec27 (bug 1578623) Backed out changeset 3badf9215788 (bug 1578623) Backed out changeset 71e58ee8f684 (bug 1578623) Backed out changeset 0128e12a910f (bug 1578623) Backed out changeset 8242877392bd (bug 1578623) Backed out changeset 6d7be8a22f8b (bug 1578623) Backed out changeset 3be609a9be36 (bug 1578623) --HG-- extra : amend_source : b76d374926d247982773b58d12cb26e33d9972e8
2768 lines
86 KiB
C++
2768 lines
86 KiB
C++
/* -*- 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/PresShell.h"
|
|
#include "mozilla/StaticPrefs_javascript.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/CanvasRenderingContext2DBinding.h"
|
|
#include "mozilla/ContentEvents.h"
|
|
#include "mozilla/CycleCollectedJSContext.h"
|
|
#include "nsCycleCollectionNoteRootCallback.h"
|
|
#include "GeckoProfiler.h"
|
|
#include "mozilla/IdleTaskRunner.h"
|
|
#include "nsIDocShell.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
|
|
|
|
// 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 TimeStamp sCurrentGCStartTime;
|
|
|
|
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), DOM and XPC
|
|
// Exceptions, and arbitrary values that were associated with a stack by the
|
|
// JS engine when they were thrown, as specified by exceptionStack.
|
|
//
|
|
// Note that the returned stackObj and stackGlobal are _not_ wrapped into the
|
|
// compartment of exceptionValue.
|
|
void FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
|
|
JS::HandleValue exceptionValue,
|
|
JS::HandleObject exceptionStack,
|
|
JS::MutableHandleObject stackObj,
|
|
JS::MutableHandleObject stackGlobal) {
|
|
stackObj.set(nullptr);
|
|
stackGlobal.set(nullptr);
|
|
|
|
if (!exceptionValue.isObject()) {
|
|
// Use the stack provided by the JS engine, if available. This will not be
|
|
// a wrapper.
|
|
if (exceptionStack) {
|
|
stackObj.set(exceptionStack);
|
|
stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (win && win->AsGlobal()->IsDying()) {
|
|
// Pretend like we have no stack, so we don't end up keeping the global
|
|
// alive via the stack.
|
|
return;
|
|
}
|
|
|
|
JS::RootingContext* rcx = RootingCx();
|
|
JS::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) {
|
|
// As above, use the stack provided by the JS engine, if available.
|
|
if (exceptionStack) {
|
|
stackObj.set(exceptionStack);
|
|
stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
|
|
if (!stack) {
|
|
return;
|
|
}
|
|
JS::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()) {
|
|
nsDependentString data(aData);
|
|
if (data.EqualsLiteral("low-memory-ongoing")) {
|
|
// Don't GC/CC if we are in an ongoing low-memory state since its very
|
|
// slow and it likely won't help us anyway.
|
|
return NS_OK;
|
|
}
|
|
if (data.EqualsLiteral("low-memory")) {
|
|
nsJSContext::SetLowMemoryState(true);
|
|
}
|
|
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, "memory-pressure-stop")) {
|
|
nsJSContext::SetLowMemoryState(false);
|
|
} 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,
|
|
JS::Handle<JSObject*> aErrorStack)
|
|
: mozilla::Runnable("ScriptErrorEvent"),
|
|
mWindow(aWindow),
|
|
mReport(aReport),
|
|
mError(aRootingCx, aError),
|
|
mErrorStack(aRootingCx, aErrorStack) {}
|
|
|
|
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, mErrorStack, &stack,
|
|
&stackGlobal);
|
|
mReport->LogToConsoleWithStack(stack, stackGlobal,
|
|
JS::ExceptionTimeWarpTarget(mError));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsPIDOMWindowInner> mWindow;
|
|
RefPtr<xpc::ErrorReport> mReport;
|
|
JS::PersistentRootedValue mError;
|
|
JS::PersistentRootedObject mErrorStack;
|
|
|
|
static bool sHandlingScriptError;
|
|
};
|
|
|
|
bool ScriptErrorEvent::sHandlingScriptError = false;
|
|
|
|
// This temporarily lives here to avoid code churn. It will go away entirely
|
|
// soon.
|
|
namespace xpc {
|
|
|
|
void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
|
|
JS::RootingContext* rootingCx,
|
|
xpc::ErrorReport* xpcReport,
|
|
JS::Handle<JS::Value> exception,
|
|
JS::Handle<JSObject*> exceptionStack) {
|
|
nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
|
|
win, rootingCx, xpcReport, exception, exceptionStack));
|
|
}
|
|
|
|
} /* namespace xpc */
|
|
|
|
#ifdef DEBUG
|
|
// A couple of useful functions to call when you're debugging.
|
|
nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
|
|
return xpc::WindowOrNull(obj);
|
|
}
|
|
|
|
template <typename T>
|
|
void PrintWinURI(T* win) {
|
|
if (!win) {
|
|
printf("No window passed in.\n");
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<Document> doc = win->GetExtantDoc();
|
|
if (!doc) {
|
|
printf("No document in the window.\n");
|
|
return;
|
|
}
|
|
|
|
nsIURI* uri = doc->GetDocumentURI();
|
|
if (!uri) {
|
|
printf("Document doesn't have a URI.\n");
|
|
return;
|
|
}
|
|
|
|
printf("%s\n", uri->GetSpecOrDefault().get());
|
|
}
|
|
|
|
void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
|
|
|
|
void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
|
|
|
|
template <typename T>
|
|
void PrintWinCodebase(T* win) {
|
|
if (!win) {
|
|
printf("No window passed in.\n");
|
|
return;
|
|
}
|
|
|
|
nsIPrincipal* prin = win->GetPrincipal();
|
|
if (!prin) {
|
|
printf("Window doesn't have principals.\n");
|
|
return;
|
|
}
|
|
|
|
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();
|
|
|
|
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->mGCOnDestruction = false;
|
|
tmp->mWindowProxy = nullptr;
|
|
tmp->Destroy();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
|
|
NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
|
|
|
|
#ifdef DEBUG
|
|
bool AtomIsEventHandlerName(nsAtom* aName) {
|
|
const char16_t* name = aName->GetUTF16String();
|
|
|
|
const char16_t* cp;
|
|
char16_t c;
|
|
for (cp = name; *cp != '\0'; ++cp) {
|
|
c = *cp;
|
|
if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
|
|
// Note: this could probably be simplified somewhat more; see bug 974327
|
|
// comments 1 and 3.
|
|
if (!mWindowProxy) {
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_ASSERT(mGlobalObjectRef);
|
|
return mGlobalObjectRef;
|
|
}
|
|
|
|
nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
|
|
const char* aPropName, nsISupports* aArgs) {
|
|
AutoJSAPI jsapi;
|
|
if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
JSContext* cx = jsapi.cx();
|
|
|
|
JS::RootedVector<JS::Value> args(cx);
|
|
|
|
JS::Rooted<JSObject*> global(cx, GetWindowProxy());
|
|
nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// got the arguments, now attach them.
|
|
|
|
for (uint32_t i = 0; i < args.length(); ++i) {
|
|
if (!JS_WrapValue(cx, args[i])) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JSObject*> array(cx, ::JS_NewArrayObject(cx, args));
|
|
if (!array) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
|
|
: NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult nsJSContext::ConvertSupportsTojsvals(
|
|
JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
|
|
JS::MutableHandleVector<JS::Value> aArgsOut) {
|
|
nsresult rv = NS_OK;
|
|
|
|
// If the array implements nsIJSArgArray, copy the contents and return.
|
|
nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
|
|
if (fastArray) {
|
|
uint32_t argc;
|
|
JS::Value* argv;
|
|
rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
|
|
if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// Take the slower path converting each item.
|
|
// Handle only nsIArray and nsIVariant. nsIArray is only needed for
|
|
// SetProperty('arguments', ...);
|
|
|
|
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
|
NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
|
|
|
|
if (!aArgs) return NS_OK;
|
|
uint32_t argCount;
|
|
// This general purpose function may need to convert an arg array
|
|
// (window.arguments, event-handler args) and a generic property.
|
|
nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
|
|
|
|
if (argsArray) {
|
|
rv = argsArray->GetLength(&argCount);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (argCount == 0) return NS_OK;
|
|
} else {
|
|
argCount = 1; // the nsISupports which is not an array
|
|
}
|
|
|
|
// Use the caller's auto guards to release and unroot.
|
|
if (!aArgsOut.resize(argCount)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (argsArray) {
|
|
for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
|
|
nsCOMPtr<nsISupports> arg;
|
|
JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
|
|
argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
|
|
getter_AddRefs(arg));
|
|
if (!arg) {
|
|
thisVal.setNull();
|
|
continue;
|
|
}
|
|
nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
|
|
if (variant != nullptr) {
|
|
rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
|
|
} else {
|
|
// And finally, support the nsISupportsPrimitives supplied
|
|
// by the AppShell. It generally will pass only strings, but
|
|
// as we have code for handling all, we may as well use it.
|
|
rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
|
|
if (rv == NS_ERROR_NO_INTERFACE) {
|
|
// something else - probably an event object or similar -
|
|
// just wrap it.
|
|
#ifdef DEBUG
|
|
// but first, check its not another nsISupportsPrimitive, as
|
|
// these are now deprecated for use with script contexts.
|
|
nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
|
|
NS_ASSERTION(prim == nullptr,
|
|
"Don't pass nsISupportsPrimitives - use nsIVariant!");
|
|
#endif
|
|
JSAutoRealm ar(aCx, aScope);
|
|
rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
|
|
if (variant) {
|
|
rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
|
|
} else {
|
|
NS_ERROR("Not an array, not an interface?");
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// This really should go into xpconnect somewhere...
|
|
nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
|
|
nsISupports* aArg,
|
|
JS::Value* aArgv) {
|
|
MOZ_ASSERT(aArg, "Empty arg");
|
|
|
|
nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
|
|
if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
|
|
|
|
uint16_t type;
|
|
argPrimitive->GetType(&type);
|
|
|
|
switch (type) {
|
|
case nsISupportsPrimitive::TYPE_CSTRING: {
|
|
nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
nsAutoCString data;
|
|
|
|
p->GetData(data);
|
|
|
|
JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
|
|
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
aArgv->setString(str);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_STRING: {
|
|
nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
nsAutoString data;
|
|
|
|
p->GetData(data);
|
|
|
|
// cast is probably safe since wchar_t and char16_t are expected
|
|
// to be equivalent; both unsigned 16-bit entities
|
|
JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
|
|
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
aArgv->setString(str);
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRBOOL: {
|
|
nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
bool data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setBoolean(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRUINT8: {
|
|
nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
uint8_t data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setInt32(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRUINT16: {
|
|
nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
uint16_t data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setInt32(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRUINT32: {
|
|
nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
uint32_t data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setInt32(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_CHAR: {
|
|
nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
char data;
|
|
|
|
p->GetData(&data);
|
|
|
|
JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
|
|
NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
aArgv->setString(str);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRINT16: {
|
|
nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
int16_t data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setInt32(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_PRINT32: {
|
|
nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
int32_t data;
|
|
|
|
p->GetData(&data);
|
|
|
|
aArgv->setInt32(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_FLOAT: {
|
|
nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
float data;
|
|
|
|
p->GetData(&data);
|
|
|
|
*aArgv = ::JS_NumberValue(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_DOUBLE: {
|
|
nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
double data;
|
|
|
|
p->GetData(&data);
|
|
|
|
*aArgv = ::JS_NumberValue(data);
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
|
|
nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
|
|
NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
|
|
|
|
nsCOMPtr<nsISupports> data;
|
|
nsIID* iid = nullptr;
|
|
|
|
p->GetData(getter_AddRefs(data));
|
|
p->GetDataIID(&iid);
|
|
NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
|
|
|
|
AutoFree iidGuard(iid); // Free iid upon destruction.
|
|
|
|
JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
|
|
JS::Rooted<JS::Value> v(aCx);
|
|
JSAutoRealm ar(aCx, scope);
|
|
nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
*aArgv = v;
|
|
|
|
break;
|
|
}
|
|
case nsISupportsPrimitive::TYPE_ID:
|
|
case nsISupportsPrimitive::TYPE_PRUINT64:
|
|
case nsISupportsPrimitive::TYPE_PRINT64:
|
|
case nsISupportsPrimitive::TYPE_PRTIME: {
|
|
NS_WARNING("Unsupported primitive type used");
|
|
aArgv->setNull();
|
|
break;
|
|
}
|
|
default: {
|
|
NS_WARNING("Unknown primitive type used");
|
|
aArgv->setNull();
|
|
break;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef MOZ_JPROF
|
|
|
|
# include <signal.h>
|
|
|
|
inline bool IsJProfAction(struct sigaction* action) {
|
|
return (action->sa_sigaction &&
|
|
(action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
|
|
(SA_RESTART | SA_SIGINFO));
|
|
}
|
|
|
|
void NS_JProfStartProfiling();
|
|
void NS_JProfStopProfiling();
|
|
void NS_JProfClearCircular();
|
|
|
|
static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
|
|
NS_JProfStartProfiling();
|
|
return true;
|
|
}
|
|
|
|
void NS_JProfStartProfiling() {
|
|
// Figure out whether we're dealing with SIGPROF, SIGALRM, or
|
|
// SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
|
|
// JP_RTC_HZ)
|
|
struct sigaction action;
|
|
|
|
// Must check ALRM before PROF since both are enabled for real-time
|
|
sigaction(SIGALRM, nullptr, &action);
|
|
// printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
if (IsJProfAction(&action)) {
|
|
// printf("Beginning real-time jprof profiling.\n");
|
|
raise(SIGALRM);
|
|
return;
|
|
}
|
|
|
|
sigaction(SIGPROF, nullptr, &action);
|
|
// printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
if (IsJProfAction(&action)) {
|
|
// printf("Beginning process-time jprof profiling.\n");
|
|
raise(SIGPROF);
|
|
return;
|
|
}
|
|
|
|
sigaction(SIGPOLL, nullptr, &action);
|
|
// printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
|
|
if (IsJProfAction(&action)) {
|
|
// printf("Beginning rtc-based jprof profiling.\n");
|
|
raise(SIGPOLL);
|
|
return;
|
|
}
|
|
|
|
printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
|
|
}
|
|
|
|
static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
|
|
NS_JProfStopProfiling();
|
|
return true;
|
|
}
|
|
|
|
void NS_JProfStopProfiling() {
|
|
raise(SIGUSR1);
|
|
// printf("Stopped jprof profiling.\n");
|
|
}
|
|
|
|
static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
|
|
NS_JProfClearCircular();
|
|
return true;
|
|
}
|
|
|
|
void NS_JProfClearCircular() {
|
|
raise(SIGUSR2);
|
|
// printf("cleared jprof buffer\n");
|
|
}
|
|
|
|
static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
|
|
// Not ideal...
|
|
NS_JProfStopProfiling();
|
|
NS_JProfStartProfiling();
|
|
return true;
|
|
}
|
|
|
|
static const JSFunctionSpec JProfFunctions[] = {
|
|
JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
|
|
JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
|
|
JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
|
|
JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
|
|
|
|
#endif /* defined(MOZ_JPROF) */
|
|
|
|
nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext* cx = jsapi.cx();
|
|
JSAutoRealm ar(cx, aGlobalObj);
|
|
|
|
#ifdef MOZ_JPROF
|
|
// Attempt to initialize JProf functions
|
|
::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
|
|
|
|
void nsJSContext::SetProcessingScriptTag(bool aFlag) {
|
|
mProcessingScriptTag = aFlag;
|
|
}
|
|
|
|
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::SetLowMemoryState(bool aState) {
|
|
JSContext* cx = danger::GetJSContext();
|
|
JS::SetLowMemoryState(cx, aState);
|
|
}
|
|
|
|
// static
|
|
void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
|
|
IsIncremental aIncremental,
|
|
IsShrinking aShrinking,
|
|
int64_t aSliceMillis) {
|
|
AUTO_PROFILER_LABEL_DYNAMIC_CSTR("nsJSContext::GarbageCollectNow", GCCC,
|
|
JS::ExplainGCReason(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
|
|
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()) {
|
|
MOZ_ASSERT(StaticPrefs::javascript_options_gc_delay() > kMaxICCDuration,
|
|
"A max duration ICC shouldn't reduce GC delay to 0");
|
|
|
|
PokeGC(JS::GCReason::CC_WAITING, nullptr,
|
|
StaticPrefs::javascript_options_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) {
|
|
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) : 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);
|
|
}
|
|
|
|
// If the GC doesn't have any more work to do on the foreground thread (and
|
|
// e.g. is waiting for background sweeping to finish) then return false to
|
|
// make IdleTaskRunner postpone the next call a bit.
|
|
JSContext* cx = danger::GetJSContext();
|
|
return JS::IncrementalGCHasForegroundWork(cx);
|
|
}
|
|
|
|
// static
|
|
void GCTimerFired(nsITimer* aTimer, void* aClosure) {
|
|
nsJSContext::KillGCTimer();
|
|
if (sShuttingDown) {
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
return;
|
|
}
|
|
|
|
if (sInterSliceGCRunner) {
|
|
return;
|
|
}
|
|
|
|
// Now start the actual GC after initial timer has fired.
|
|
sInterSliceGCRunner = IdleTaskRunner::Create(
|
|
[aClosure](TimeStamp aDeadline) {
|
|
return InterSliceGCRunnerFired(aDeadline, aClosure);
|
|
},
|
|
"GCTimerFired::InterSliceGCRunnerFired",
|
|
StaticPrefs::javascript_options_gc_delay_interslice(),
|
|
sActiveIntersliceGCBudget, true, [] { 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;
|
|
} else if (!isLateTimerFire && !aDeadline.IsNull()) {
|
|
MOZ_ASSERT(!didDoWork);
|
|
// If we're called during idle time, try to find some work to do by calling
|
|
// the method recursively, effectively bypassing some possible forget
|
|
// skippable calls.
|
|
sCCRunnerFireCount = numEarlyTimerFires;
|
|
return CCRunnerFired(aDeadline);
|
|
}
|
|
|
|
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 aReason,
|
|
mozilla::TimeStamp aDeadline) {
|
|
if (sShuttingDown) {
|
|
return;
|
|
}
|
|
|
|
if (sGCTimer) {
|
|
if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
|
|
// Force full GCs when called from reftests so that we collect dead zones
|
|
// that have not been scheduled for collection.
|
|
sNeedsFullGC = true;
|
|
}
|
|
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) {
|
|
MOZ_ASSERT(!sICCRunner,
|
|
"Shouldn't have both sCCRunner and sICCRunner active at the "
|
|
"same time");
|
|
sCCRunner->SetDeadline(aDeadline);
|
|
runnable = sCCRunner;
|
|
} else if (sICCRunner) {
|
|
sICCRunner->SetDeadline(aDeadline);
|
|
runnable = sICCRunner;
|
|
}
|
|
}
|
|
|
|
if (runnable) {
|
|
runnable->Run();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
|
|
JS::GCReason aReason) {
|
|
if (!aDocShell || !XRE_IsContentProcess()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShellTreeItem> root;
|
|
aDocShell->GetInProcessSameTypeRootTreeItem(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;
|
|
}
|
|
|
|
PresShell* presShell = rootDocument->GetPresShell();
|
|
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) >
|
|
(StaticPrefs::dom_events_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 aReason, JSObject* aObj,
|
|
uint32_t 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 ? StaticPrefs::javascript_options_gc_delay_first()
|
|
: StaticPrefs::javascript_options_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;
|
|
}
|
|
|
|
// Don't run consecutive CCs too often.
|
|
if (sCleanupsSinceLastGC && !sLastCCEndTime.IsNull()) {
|
|
uint32_t sinceLastCCEnd = TimeUntilNow(sLastCCEndTime);
|
|
if (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) &&
|
|
!sLastForgetSkippableCycleEndTime.IsNull()) {
|
|
uint32_t sinceLastForgetSkippableCycle =
|
|
TimeUntilNow(sLastForgetSkippableCycleEndTime);
|
|
if (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;
|
|
sCurrentGCStartTime = TimeStamp::Now();
|
|
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,
|
|
StaticPrefs::javascript_options_gc_delay_full(),
|
|
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;
|
|
}
|
|
|
|
Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
|
|
TimeBetween(sCurrentGCStartTime, TimeStamp::Now()));
|
|
break;
|
|
}
|
|
|
|
case JS::GC_SLICE_BEGIN:
|
|
break;
|
|
|
|
case JS::GC_SLICE_END:
|
|
sGCUnnotifiedTotalTime +=
|
|
aDesc.lastSliceEnd(aCx) - aDesc.lastSliceStart(aCx);
|
|
|
|
if (sShuttingDown || aDesc.isComplete_) {
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
} else if (!sInterSliceGCRunner) {
|
|
// If incremental GC wasn't triggered by GCTimerFired, we may not
|
|
// have a runner to ensure all the slices are handled. So, create
|
|
// the runner here.
|
|
sInterSliceGCRunner = IdleTaskRunner::Create(
|
|
[](TimeStamp aDeadline) {
|
|
return InterSliceGCRunnerFired(aDeadline, nullptr);
|
|
},
|
|
"DOMGCSliceCallback::InterSliceGCRunnerFired",
|
|
StaticPrefs::javascript_options_gc_delay_interslice(),
|
|
sActiveIntersliceGCBudget, true, [] { 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 SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
|
|
void* aClosure) {
|
|
int32_t prefKB = Preferences::GetInt(aPrefName, -1);
|
|
// handle overflow and negative pref values
|
|
CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
|
|
if (prefB.isValid() && prefB.value() >= 0) {
|
|
SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
|
|
} else {
|
|
ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
|
|
}
|
|
}
|
|
|
|
static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
|
|
void* aClosure) {
|
|
int32_t pref = Preferences::GetInt(aPrefName, -1);
|
|
// handle overflow and negative pref values
|
|
if (pref >= 0 && pref < 10000) {
|
|
SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
|
|
} else {
|
|
ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
|
|
}
|
|
}
|
|
|
|
static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
|
|
void* aClosure) {
|
|
bool pref = Preferences::GetBool(aPrefName);
|
|
SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
|
|
}
|
|
|
|
static void 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) {
|
|
if (enableZoneGC) {
|
|
mode = JSGC_MODE_ZONE_INCREMENTAL;
|
|
} else {
|
|
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_MS, pref);
|
|
} else {
|
|
ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
|
|
}
|
|
}
|
|
|
|
static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
|
|
void* aClosure) {
|
|
bool pref = Preferences::GetBool(aPrefName);
|
|
sIncrementalCC = pref;
|
|
}
|
|
|
|
class JSDispatchableRunnable final : public Runnable {
|
|
~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
|
|
|
|
public:
|
|
explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
|
|
: mozilla::Runnable("JSDispatchableRunnable"),
|
|
mDispatchable(aDispatchable) {
|
|
MOZ_ASSERT(mDispatchable);
|
|
}
|
|
|
|
protected:
|
|
NS_IMETHOD Run() override {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
|
|
JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
|
|
sShuttingDown ? JS::Dispatchable::ShuttingDown
|
|
: JS::Dispatchable::NotShuttingDown;
|
|
|
|
mDispatchable->run(jsapi.cx(), maybeShuttingDown);
|
|
mDispatchable = nullptr; // mDispatchable may delete itself
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
JS::Dispatchable* mDispatchable;
|
|
};
|
|
|
|
static bool DispatchToEventLoop(void* closure,
|
|
JS::Dispatchable* aDispatchable) {
|
|
MOZ_ASSERT(!closure);
|
|
|
|
// This callback may execute either on the main thread or a random JS-internal
|
|
// helper thread. This callback can be called during shutdown so we cannot
|
|
// simply NS_DispatchToMainThread. Failure during shutdown is expected and
|
|
// properly handled by the JS engine.
|
|
|
|
nsCOMPtr<nsIEventTarget> mainTarget = 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);
|
|
|
|
JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
|
|
JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
|
|
FetchUtil::ReportJSStreamError);
|
|
|
|
// Set these global xpconnect options...
|
|
Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
|
|
"javascript.options.mem.max",
|
|
(void*)JSGC_MAX_BYTES);
|
|
Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
|
|
"javascript.options.mem.nursery.min_kb",
|
|
(void*)JSGC_MIN_NURSERY_BYTES);
|
|
Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
|
|
"javascript.options.mem.nursery.max_kb",
|
|
(void*)JSGC_MAX_NURSERY_BYTES);
|
|
|
|
Preferences::RegisterCallbackAndCall(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_non_incremental_factor",
|
|
(void*)JSGC_NON_INCREMENTAL_FACTOR);
|
|
Preferences::RegisterCallbackAndCall(
|
|
SetMemoryPrefChangedCallbackInt,
|
|
"javascript.options.mem.gc_avoid_interrupt_factor",
|
|
(void*)JSGC_AVOID_INTERRUPT_FACTOR);
|
|
|
|
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;
|
|
}
|