forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2727 lines
		
	
	
	
		
			84 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2727 lines
		
	
	
	
		
			84 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/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/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 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_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) : 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 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 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 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;
 | |
| }
 | |
| 
 | |
| 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.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;
 | |
| }
 | 
