michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsError.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIDOMChromeWindow.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsDOMCID.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIJSRuntimeService.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "nsIContent.h" michael@0: #include "nsCycleCollector.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsIXULRuntime.h" michael@0: #include "nsTextFormatter.h" michael@0: michael@0: #include "xpcpublic.h" michael@0: michael@0: #include "js/OldDebugAPI.h" michael@0: #include "jswrapper.h" michael@0: #include "nsIArray.h" michael@0: #include "nsIObjectInputStream.h" michael@0: #include "nsIObjectOutputStream.h" michael@0: #include "prmem.h" michael@0: #include "WrapperFactory.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsScriptNameSpaceManager.h" michael@0: #include "StructuredCloneTags.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "mozilla/dom/ErrorEvent.h" michael@0: #include "mozilla/dom/ImageData.h" michael@0: #include "mozilla/dom/ImageDataBinding.h" michael@0: #include "nsAXPCNativeCallContext.h" michael@0: #include "mozilla/CycleCollectedJSRuntime.h" michael@0: michael@0: #include "nsJSPrincipals.h" michael@0: michael@0: #ifdef XP_MACOSX michael@0: // AssertMacros.h defines 'check' and conflicts with AccessCheck.h michael@0: #undef check michael@0: #endif michael@0: #include "AccessCheck.h" michael@0: michael@0: #ifdef MOZ_JSDEBUGGER michael@0: #include "jsdIDebuggerService.h" michael@0: #endif michael@0: #ifdef MOZ_LOGGING michael@0: // Force PR_LOGGING so we can get JS strict warnings even in release builds michael@0: #define FORCE_PR_LOG 1 michael@0: #endif michael@0: #include "prlog.h" michael@0: #include "prthread.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/dom/asmjscache/AsmJSCache.h" michael@0: #include "mozilla/dom/CanvasRenderingContext2DBinding.h" michael@0: #include "mozilla/CycleCollectedJSRuntime.h" michael@0: #include "mozilla/ContentEvents.h" michael@0: michael@0: #include "nsCycleCollectionNoteRootCallback.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: const size_t gStackSize = 8192; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gJSDiagnostics; michael@0: #endif michael@0: michael@0: // Thank you Microsoft! michael@0: #ifdef CompareString michael@0: #undef CompareString michael@0: #endif michael@0: michael@0: #define NS_SHRINK_GC_BUFFERS_DELAY 4000 // ms michael@0: michael@0: // The amount of time we wait from the first request to GC to actually michael@0: // doing the first GC. michael@0: #define NS_FIRST_GC_DELAY 10000 // ms michael@0: michael@0: #define NS_FULL_GC_DELAY 60000 // ms michael@0: michael@0: // Maximum amount of time that should elapse between incremental GC slices michael@0: #define NS_INTERSLICE_GC_DELAY 100 // ms michael@0: michael@0: // If we haven't painted in 100ms, we allow for a longer GC budget michael@0: #define NS_INTERSLICE_GC_BUDGET 40 // ms michael@0: michael@0: // The amount of time we wait between a request to CC (after GC ran) michael@0: // and doing the actual CC. michael@0: #define NS_CC_DELAY 6000 // ms michael@0: michael@0: #define NS_CC_SKIPPABLE_DELAY 400 // ms michael@0: michael@0: // Maximum amount of time that should elapse between incremental CC slices michael@0: static const int64_t kICCIntersliceDelay = 32; // ms michael@0: michael@0: // Time budget for an incremental CC slice michael@0: static const int64_t kICCSliceBudget = 10; // ms michael@0: michael@0: // Maximum total duration for an ICC michael@0: static const uint32_t kMaxICCDuration = 2000; // ms michael@0: michael@0: // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT michael@0: // objects in the purple buffer. michael@0: #define NS_CC_FORCED (2 * 60 * PR_USEC_PER_SEC) // 2 min michael@0: #define NS_CC_FORCED_PURPLE_LIMIT 10 michael@0: michael@0: // Don't allow an incremental GC to lock out the CC for too long. michael@0: #define NS_MAX_CC_LOCKEDOUT_TIME (15 * PR_USEC_PER_SEC) // 15 seconds michael@0: michael@0: // Trigger a CC if the purple buffer exceeds this size when we check it. michael@0: #define NS_CC_PURPLE_LIMIT 200 michael@0: michael@0: #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT michael@0: michael@0: // Large value used to specify that a script should run essentially forever michael@0: #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32) michael@0: michael@0: #define NS_MAJOR_FORGET_SKIPPABLE_CALLS 2 michael@0: michael@0: // if you add statics here, add them to the list in StartupJSEnvironment michael@0: michael@0: static nsITimer *sGCTimer; michael@0: static nsITimer *sShrinkGCBuffersTimer; michael@0: static nsITimer *sCCTimer; michael@0: static nsITimer *sICCTimer; michael@0: static nsITimer *sFullGCTimer; michael@0: static nsITimer *sInterSliceGCTimer; michael@0: michael@0: static TimeStamp sLastCCEndTime; michael@0: michael@0: static bool sCCLockedOut; michael@0: static PRTime sCCLockedOutTime; michael@0: michael@0: static JS::GCSliceCallback sPrevGCSliceCallback; michael@0: michael@0: static bool sHasRunGC; michael@0: michael@0: // The number of currently pending document loads. This count isn't michael@0: // guaranteed to always reflect reality and can't easily as we don't michael@0: // have an easy place to know when a load ends or is interrupted in michael@0: // all cases. This counter also gets reset if we end up GC'ing while michael@0: // we're waiting for a slow page to load. IOW, this count may be 0 michael@0: // even when there are pending loads. michael@0: static uint32_t sPendingLoadCount; michael@0: static bool sLoadingInProgress; michael@0: michael@0: static uint32_t sCCollectedWaitingForGC; michael@0: static uint32_t sLikelyShortLivingObjectsNeedingGC; michael@0: static bool sPostGCEventsToConsole; michael@0: static bool sPostGCEventsToObserver; michael@0: static int32_t sCCTimerFireCount = 0; michael@0: static uint32_t sMinForgetSkippableTime = UINT32_MAX; michael@0: static uint32_t sMaxForgetSkippableTime = 0; michael@0: static uint32_t sTotalForgetSkippableTime = 0; michael@0: static uint32_t sRemovedPurples = 0; michael@0: static uint32_t sForgetSkippableBeforeCC = 0; michael@0: static uint32_t sPreviousSuspectedCount = 0; michael@0: static uint32_t sCleanupsSinceLastGC = UINT32_MAX; michael@0: static bool sNeedsFullCC = false; michael@0: static bool sNeedsGCAfterCC = false; michael@0: static bool sIncrementalCC = false; michael@0: michael@0: static nsScriptNameSpaceManager *gNameSpaceManager; michael@0: michael@0: static nsIJSRuntimeService *sRuntimeService; michael@0: michael@0: static const char kJSRuntimeServiceContractID[] = michael@0: "@mozilla.org/js/xpc/RuntimeService;1"; michael@0: michael@0: static PRTime sFirstCollectionTime; michael@0: michael@0: static JSRuntime *sRuntime; michael@0: michael@0: static bool sIsInitialized; michael@0: static bool sDidShutdown; michael@0: static bool sShuttingDown; michael@0: static int32_t sContextCount; michael@0: michael@0: static nsIScriptSecurityManager *sSecurityManager; michael@0: michael@0: // nsJSEnvironmentObserver observes the memory-pressure notifications michael@0: // and forces a garbage collection and cycle collection when it happens, if michael@0: // the appropriate pref is set. michael@0: michael@0: static bool sGCOnMemoryPressure; michael@0: michael@0: // In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more michael@0: // aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps michael@0: // us from triggering expensive full collections too frequently. michael@0: static int32_t sExpensiveCollectorPokes = 0; michael@0: static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5; michael@0: michael@0: static PRTime michael@0: GetCollectionTimeDelta() michael@0: { michael@0: PRTime now = PR_Now(); michael@0: if (sFirstCollectionTime) { michael@0: return now - sFirstCollectionTime; michael@0: } michael@0: sFirstCollectionTime = now; michael@0: return 0; michael@0: } michael@0: michael@0: static void michael@0: KillTimers() michael@0: { michael@0: nsJSContext::KillGCTimer(); michael@0: nsJSContext::KillShrinkGCBuffersTimer(); michael@0: nsJSContext::KillCCTimer(); michael@0: nsJSContext::KillICCTimer(); michael@0: nsJSContext::KillFullGCTimer(); michael@0: nsJSContext::KillInterSliceGCTimer(); michael@0: } michael@0: michael@0: // If we collected a substantial amount of cycles, poke the GC since more objects michael@0: // might be unreachable now. michael@0: static bool michael@0: NeedsGCAfterCC() michael@0: { michael@0: return sCCollectedWaitingForGC > 250 || michael@0: sLikelyShortLivingObjectsNeedingGC > 2500 || michael@0: sNeedsGCAfterCC; michael@0: } michael@0: michael@0: class nsJSEnvironmentObserver MOZ_FINAL : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (sGCOnMemoryPressure && !nsCRT::strcmp(aTopic, "memory-pressure")) { michael@0: if(StringBeginsWith(nsDependentString(aData), michael@0: NS_LITERAL_STRING("low-memory-ongoing"))) { michael@0: // Don't GC/CC if we are in an ongoing low-memory state since its very michael@0: // slow and it likely won't help us anyway. michael@0: return NS_OK; michael@0: } michael@0: nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE, michael@0: nsJSContext::NonIncrementalGC, michael@0: nsJSContext::NonCompartmentGC, michael@0: nsJSContext::ShrinkingGC); michael@0: nsJSContext::CycleCollectNow(); michael@0: if (NeedsGCAfterCC()) { michael@0: nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE, michael@0: nsJSContext::NonIncrementalGC, michael@0: nsJSContext::NonCompartmentGC, michael@0: nsJSContext::ShrinkingGC); michael@0: } michael@0: } else if (!nsCRT::strcmp(aTopic, "quit-application")) { michael@0: sShuttingDown = true; michael@0: KillTimers(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /**************************************************************** michael@0: ************************** AutoFree **************************** michael@0: ****************************************************************/ michael@0: michael@0: class AutoFree { michael@0: public: michael@0: AutoFree(void *aPtr) : mPtr(aPtr) { michael@0: } michael@0: ~AutoFree() { michael@0: if (mPtr) michael@0: nsMemory::Free(mPtr); michael@0: } michael@0: void Invalidate() { michael@0: mPtr = 0; michael@0: } michael@0: private: michael@0: void *mPtr; michael@0: }; michael@0: michael@0: // A utility function for script languages to call. Although it looks small, michael@0: // the use of nsIDocShell and nsPresContext triggers a huge number of michael@0: // dependencies that most languages would not otherwise need. michael@0: // XXXmarkh - This function is mis-placed! michael@0: bool michael@0: NS_HandleScriptError(nsIScriptGlobalObject *aScriptGlobal, michael@0: const ErrorEventInit &aErrorEventInit, michael@0: nsEventStatus *aStatus) michael@0: { michael@0: bool called = false; michael@0: nsCOMPtr win(do_QueryInterface(aScriptGlobal)); michael@0: nsIDocShell *docShell = win ? win->GetDocShell() : nullptr; michael@0: if (docShell) { michael@0: nsRefPtr presContext; michael@0: docShell->GetPresContext(getter_AddRefs(presContext)); michael@0: michael@0: static int32_t errorDepth; // Recursion prevention michael@0: ++errorDepth; michael@0: michael@0: if (errorDepth < 2) { michael@0: // Dispatch() must be synchronous for the recursion block michael@0: // (errorDepth) to work. michael@0: nsRefPtr event = michael@0: ErrorEvent::Constructor(static_cast(win.get()), michael@0: NS_LITERAL_STRING("error"), michael@0: aErrorEventInit); michael@0: event->SetTrusted(true); michael@0: michael@0: EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext, michael@0: aStatus); michael@0: called = true; michael@0: } michael@0: --errorDepth; michael@0: } michael@0: return called; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: AsyncErrorReporter::AsyncErrorReporter(JSRuntime* aRuntime, michael@0: JSErrorReport* aErrorReport, michael@0: const char* aFallbackMessage, michael@0: bool aIsChromeError, michael@0: nsPIDOMWindow* aWindow) michael@0: : mSourceLine(static_cast(aErrorReport->uclinebuf)) michael@0: , mLineNumber(aErrorReport->lineno) michael@0: , mColumn(aErrorReport->column) michael@0: , mFlags(aErrorReport->flags) michael@0: { michael@0: if (!aErrorReport->filename) { michael@0: mFileName.SetIsVoid(true); michael@0: } else { michael@0: mFileName.AssignWithConversion(aErrorReport->filename); michael@0: } michael@0: michael@0: const char16_t* m = static_cast(aErrorReport->ucmessage); michael@0: if (m) { michael@0: const char16_t* n = static_cast michael@0: (js::GetErrorTypeName(aRuntime, aErrorReport->exnType)); michael@0: if (n) { michael@0: mErrorMsg.Assign(n); michael@0: mErrorMsg.AppendLiteral(": "); michael@0: } michael@0: mErrorMsg.Append(m); michael@0: } michael@0: michael@0: if (mErrorMsg.IsEmpty() && aFallbackMessage) { michael@0: mErrorMsg.AssignWithConversion(aFallbackMessage); michael@0: } michael@0: michael@0: mCategory = aIsChromeError ? NS_LITERAL_CSTRING("chrome javascript") : michael@0: NS_LITERAL_CSTRING("content javascript"); michael@0: michael@0: mInnerWindowID = 0; michael@0: if (aWindow && aWindow->IsOuterWindow()) { michael@0: aWindow = aWindow->GetCurrentInnerWindow(); michael@0: } michael@0: if (aWindow) { michael@0: mInnerWindowID = aWindow->WindowID(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AsyncErrorReporter::ReportError() michael@0: { michael@0: nsCOMPtr errorObject = michael@0: do_CreateInstance("@mozilla.org/scripterror;1"); michael@0: if (!errorObject) { michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = errorObject->InitWithWindowID(mErrorMsg, mFileName, michael@0: mSourceLine, mLineNumber, michael@0: mColumn, mFlags, mCategory, michael@0: mInnerWindowID); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (!consoleService) { michael@0: return; michael@0: } michael@0: michael@0: consoleService->LogMessage(errorObject); michael@0: return; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: michael@0: class ScriptErrorEvent : public AsyncErrorReporter michael@0: { michael@0: public: michael@0: ScriptErrorEvent(nsIScriptGlobalObject* aScriptGlobal, michael@0: JSRuntime* aRuntime, michael@0: JSErrorReport* aErrorReport, michael@0: const char* aFallbackMessage, michael@0: nsIPrincipal* aScriptOriginPrincipal, michael@0: nsIPrincipal* aGlobalPrincipal, michael@0: nsPIDOMWindow* aWindow, michael@0: JS::Handle aError, michael@0: bool aDispatchEvent) michael@0: // Pass an empty category, then compute ours michael@0: : AsyncErrorReporter(aRuntime, aErrorReport, aFallbackMessage, michael@0: nsContentUtils::IsSystemPrincipal(aGlobalPrincipal), michael@0: aWindow) michael@0: , mScriptGlobal(aScriptGlobal) michael@0: , mOriginPrincipal(aScriptOriginPrincipal) michael@0: , mDispatchEvent(aDispatchEvent) michael@0: , mError(aRuntime, aError) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsEventStatus status = nsEventStatus_eIgnore; michael@0: // First, notify the DOM that we have a script error. michael@0: if (mDispatchEvent) { michael@0: nsCOMPtr win(do_QueryInterface(mScriptGlobal)); michael@0: nsIDocShell* docShell = win ? win->GetDocShell() : nullptr; michael@0: if (docShell && michael@0: !JSREPORT_IS_WARNING(mFlags) && michael@0: !sHandlingScriptError) { michael@0: AutoRestore recursionGuard(sHandlingScriptError); michael@0: sHandlingScriptError = true; michael@0: michael@0: nsRefPtr presContext; michael@0: docShell->GetPresContext(getter_AddRefs(presContext)); michael@0: michael@0: ThreadsafeAutoJSContext cx; michael@0: RootedDictionary init(cx); michael@0: init.mCancelable = true; michael@0: init.mFilename = mFileName; michael@0: init.mBubbles = true; michael@0: michael@0: nsCOMPtr sop(do_QueryInterface(win)); michael@0: NS_ENSURE_STATE(sop); michael@0: nsIPrincipal* p = sop->GetPrincipal(); michael@0: NS_ENSURE_STATE(p); michael@0: michael@0: bool sameOrigin = !mOriginPrincipal; michael@0: michael@0: if (p && !sameOrigin) { michael@0: if (NS_FAILED(p->Subsumes(mOriginPrincipal, &sameOrigin))) { michael@0: sameOrigin = false; michael@0: } michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(xoriginMsg, "Script error."); michael@0: if (sameOrigin) { michael@0: init.mMessage = mErrorMsg; michael@0: init.mLineno = mLineNumber; michael@0: init.mColno = mColumn; michael@0: init.mError = mError; michael@0: } else { michael@0: NS_WARNING("Not same origin error!"); michael@0: init.mMessage = xoriginMsg; michael@0: init.mLineno = 0; michael@0: } michael@0: michael@0: nsRefPtr event = michael@0: ErrorEvent::Constructor(static_cast(win.get()), michael@0: NS_LITERAL_STRING("error"), init); michael@0: event->SetTrusted(true); michael@0: michael@0: EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext, michael@0: &status); michael@0: } michael@0: } michael@0: michael@0: if (status != nsEventStatus_eConsumeNoDefault) { michael@0: AsyncErrorReporter::ReportError(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mScriptGlobal; michael@0: nsCOMPtr mOriginPrincipal; michael@0: bool mDispatchEvent; michael@0: JS::PersistentRootedValue mError; michael@0: michael@0: static bool sHandlingScriptError; michael@0: }; michael@0: michael@0: bool ScriptErrorEvent::sHandlingScriptError = false; michael@0: michael@0: // NOTE: This function could be refactored to use the above. The only reason michael@0: // it has not been done is that the code below only fills the error event michael@0: // after it has a good nsPresContext - whereas using the above function michael@0: // would involve always filling it. Is that a concern? michael@0: void michael@0: NS_ScriptErrorReporter(JSContext *cx, michael@0: const char *message, michael@0: JSErrorReport *report) michael@0: { michael@0: // We don't want to report exceptions too eagerly, but warnings in the michael@0: // absence of werror are swallowed whole, so report those now. michael@0: if (!JSREPORT_IS_WARNING(report->flags)) { michael@0: nsIXPConnect* xpc = nsContentUtils::XPConnect(); michael@0: if (JS::DescribeScriptedCaller(cx)) { michael@0: xpc->MarkErrorUnreported(cx); michael@0: return; michael@0: } michael@0: michael@0: if (xpc) { michael@0: nsAXPCNativeCallContext *cc = nullptr; michael@0: xpc->GetCurrentNativeCallContext(&cc); michael@0: if (cc) { michael@0: nsAXPCNativeCallContext *prev = cc; michael@0: while (NS_SUCCEEDED(prev->GetPreviousCallContext(&prev)) && prev) { michael@0: uint16_t lang; michael@0: if (NS_SUCCEEDED(prev->GetLanguage(&lang)) && michael@0: lang == nsAXPCNativeCallContext::LANG_JS) { michael@0: xpc->MarkErrorUnreported(cx); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // XXX this means we are not going to get error reports on non DOM contexts michael@0: nsIScriptContext *context = nsJSUtils::GetDynamicScriptContext(cx); michael@0: michael@0: JS::Rooted exception(cx); michael@0: ::JS_GetPendingException(cx, &exception); michael@0: michael@0: // Note: we must do this before running any more code on cx (if cx is the michael@0: // dynamic script context). michael@0: ::JS_ClearPendingException(cx); michael@0: michael@0: if (context) { michael@0: nsIScriptGlobalObject *globalObject = context->GetGlobalObject(); michael@0: michael@0: if (globalObject) { michael@0: michael@0: nsCOMPtr win = do_QueryInterface(globalObject); michael@0: nsCOMPtr scriptPrincipal = michael@0: do_QueryInterface(globalObject); michael@0: NS_ASSERTION(scriptPrincipal, "Global objects must implement " michael@0: "nsIScriptObjectPrincipal"); michael@0: nsContentUtils::AddScriptRunner( michael@0: new ScriptErrorEvent(globalObject, michael@0: JS_GetRuntime(cx), michael@0: report, michael@0: message, michael@0: nsJSPrincipals::get(report->originPrincipals), michael@0: scriptPrincipal->GetPrincipal(), michael@0: win, michael@0: exception, michael@0: /* We do not try to report Out Of Memory via a dom michael@0: * event because the dom event handler would michael@0: * encounter an OOM exception trying to process the michael@0: * event, and then we'd need to generate a new OOM michael@0: * event for that new OOM instance -- this isn't michael@0: * pretty. michael@0: */ michael@0: report->errorNumber != JSMSG_OUT_OF_MEMORY)); michael@0: } michael@0: } michael@0: michael@0: if (nsContentUtils::DOMWindowDumpEnabled()) { michael@0: // Print it to stderr as well, for the benefit of those invoking michael@0: // mozilla with -console. michael@0: nsAutoCString error; michael@0: error.Assign("JavaScript "); michael@0: if (JSREPORT_IS_STRICT(report->flags)) michael@0: error.Append("strict "); michael@0: if (JSREPORT_IS_WARNING(report->flags)) michael@0: error.Append("warning: "); michael@0: else michael@0: error.Append("error: "); michael@0: error.Append(report->filename); michael@0: error.Append(", line "); michael@0: error.AppendInt(report->lineno, 10); michael@0: error.Append(": "); michael@0: if (report->ucmessage) { michael@0: AppendUTF16toUTF8(reinterpret_cast(report->ucmessage), michael@0: error); michael@0: } else { michael@0: error.Append(message); michael@0: } michael@0: michael@0: fprintf(stderr, "%s\n", error.get()); michael@0: fflush(stderr); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!gJSDiagnostics) michael@0: gJSDiagnostics = PR_NewLogModule("JSDiagnostics"); michael@0: michael@0: if (gJSDiagnostics) { michael@0: PR_LOG(gJSDiagnostics, michael@0: JSREPORT_IS_WARNING(report->flags) ? PR_LOG_WARNING : PR_LOG_ERROR, michael@0: ("file %s, line %u: %s\n%s%s", michael@0: report->filename, report->lineno, message, michael@0: report->linebuf ? report->linebuf : "", michael@0: (report->linebuf && michael@0: report->linebuf[strlen(report->linebuf)-1] != '\n') michael@0: ? "\n" michael@0: : "")); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: // A couple of useful functions to call when you're debugging. michael@0: nsGlobalWindow * michael@0: JSObject2Win(JSObject *obj) michael@0: { michael@0: return xpc::WindowOrNull(obj); michael@0: } michael@0: michael@0: void michael@0: PrintWinURI(nsGlobalWindow *win) michael@0: { michael@0: if (!win) { michael@0: printf("No window passed in.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr doc = win->GetExtantDoc(); michael@0: if (!doc) { michael@0: printf("No document in the window.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsIURI *uri = doc->GetDocumentURI(); michael@0: if (!uri) { michael@0: printf("Document doesn't have a URI.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: printf("%s\n", spec.get()); michael@0: } michael@0: michael@0: void michael@0: PrintWinCodebase(nsGlobalWindow *win) michael@0: { michael@0: if (!win) { michael@0: printf("No window passed in.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsIPrincipal *prin = win->GetPrincipal(); michael@0: if (!prin) { michael@0: printf("Window doesn't have principals.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: prin->GetURI(getter_AddRefs(uri)); michael@0: if (!uri) { michael@0: printf("No URI, maybe the system principal.\n"); michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: printf("%s\n", spec.get()); michael@0: } michael@0: michael@0: void michael@0: DumpString(const nsAString &str) michael@0: { michael@0: printf("%s\n", NS_ConvertUTF16toUTF8(str).get()); michael@0: } michael@0: #endif michael@0: michael@0: #define JS_OPTIONS_DOT_STR "javascript.options." michael@0: michael@0: static const char js_options_dot_str[] = JS_OPTIONS_DOT_STR; michael@0: static const char js_strict_option_str[] = JS_OPTIONS_DOT_STR "strict"; michael@0: #ifdef DEBUG michael@0: static const char js_strict_debug_option_str[] = JS_OPTIONS_DOT_STR "strict.debug"; michael@0: #endif michael@0: static const char js_werror_option_str[] = JS_OPTIONS_DOT_STR "werror"; michael@0: #ifdef JS_GC_ZEAL michael@0: static const char js_zeal_option_str[] = JS_OPTIONS_DOT_STR "gczeal"; michael@0: static const char js_zeal_frequency_str[] = JS_OPTIONS_DOT_STR "gczeal.frequency"; michael@0: #endif michael@0: static const char js_memlog_option_str[] = JS_OPTIONS_DOT_STR "mem.log"; michael@0: static const char js_memnotify_option_str[] = JS_OPTIONS_DOT_STR "mem.notify"; michael@0: michael@0: void michael@0: nsJSContext::JSOptionChangedCallback(const char *pref, void *data) michael@0: { michael@0: nsJSContext *context = reinterpret_cast(data); michael@0: JSContext *cx = context->mContext; michael@0: michael@0: sPostGCEventsToConsole = Preferences::GetBool(js_memlog_option_str); michael@0: sPostGCEventsToObserver = Preferences::GetBool(js_memnotify_option_str); michael@0: michael@0: JS::ContextOptionsRef(cx).setExtraWarnings(Preferences::GetBool(js_strict_option_str)); michael@0: michael@0: // The vanilla GetGlobalObject returns null if a global isn't set up on michael@0: // the context yet. We can sometimes be call midway through context init, michael@0: // So ask for the member directly instead. michael@0: nsIScriptGlobalObject *global = context->GetGlobalObjectRef(); michael@0: michael@0: // XXX should we check for sysprin instead of a chrome window, to make michael@0: // XXX components be covered by the chrome pref instead of the content one? michael@0: nsCOMPtr contentWindow(do_QueryInterface(global)); michael@0: nsCOMPtr chromeWindow(do_QueryInterface(global)); michael@0: michael@0: #ifdef DEBUG michael@0: // In debug builds, warnings are enabled in chrome context if michael@0: // javascript.options.strict.debug is true michael@0: if (Preferences::GetBool(js_strict_debug_option_str) && michael@0: (chromeWindow || !contentWindow)) { michael@0: JS::ContextOptionsRef(cx).setExtraWarnings(true); michael@0: } michael@0: #endif michael@0: michael@0: JS::ContextOptionsRef(cx).setWerror(Preferences::GetBool(js_werror_option_str)); michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: int32_t zeal = Preferences::GetInt(js_zeal_option_str, -1); michael@0: int32_t frequency = Preferences::GetInt(js_zeal_frequency_str, JS_DEFAULT_ZEAL_FREQ); michael@0: if (zeal >= 0) michael@0: ::JS_SetGCZeal(context->mContext, (uint8_t)zeal, frequency); michael@0: #endif michael@0: } michael@0: michael@0: nsJSContext::nsJSContext(bool aGCOnDestruction, michael@0: nsIScriptGlobalObject* aGlobalObject) michael@0: : mWindowProxy(nullptr) michael@0: , mGCOnDestruction(aGCOnDestruction) michael@0: , mGlobalObjectRef(aGlobalObject) michael@0: { michael@0: EnsureStatics(); michael@0: michael@0: ++sContextCount; michael@0: michael@0: mContext = ::JS_NewContext(sRuntime, gStackSize); michael@0: if (mContext) { michael@0: ::JS_SetContextPrivate(mContext, static_cast(this)); michael@0: michael@0: // Make sure the new context gets the default context options michael@0: JS::ContextOptionsRef(mContext).setPrivateIsNSISupports(true) michael@0: .setNoDefaultCompartmentObject(true); michael@0: michael@0: // Watch for the JS boolean options michael@0: Preferences::RegisterCallback(JSOptionChangedCallback, michael@0: js_options_dot_str, this); michael@0: } michael@0: mIsInitialized = false; michael@0: mProcessingScriptTag = false; michael@0: HoldJSObjects(this); michael@0: } michael@0: michael@0: nsJSContext::~nsJSContext() michael@0: { michael@0: mGlobalObjectRef = nullptr; michael@0: michael@0: DestroyJSContext(); michael@0: michael@0: --sContextCount; michael@0: michael@0: if (!sContextCount && sDidShutdown) { michael@0: // The last context is being deleted, and we're already in the michael@0: // process of shutting down, release the JS runtime service, and michael@0: // the security manager. michael@0: michael@0: NS_IF_RELEASE(sRuntimeService); michael@0: NS_IF_RELEASE(sSecurityManager); michael@0: } michael@0: } michael@0: michael@0: // This function is called either by the destructor or unlink, which means that michael@0: // it can never be called when there is an outstanding ref to the michael@0: // nsIScriptContext on the stack. Our stack-scoped cx pushers hold such a ref, michael@0: // so we can assume here that mContext is not on the stack (and therefore not michael@0: // in use). michael@0: void michael@0: nsJSContext::DestroyJSContext() michael@0: { michael@0: if (!mContext) { michael@0: return; michael@0: } michael@0: michael@0: // Clear our entry in the JSContext, bugzilla bug 66413 michael@0: ::JS_SetContextPrivate(mContext, nullptr); michael@0: michael@0: // Unregister our "javascript.options.*" pref-changed callback. michael@0: Preferences::UnregisterCallback(JSOptionChangedCallback, michael@0: js_options_dot_str, this); michael@0: michael@0: if (mGCOnDestruction) { michael@0: PokeGC(JS::gcreason::NSJSCONTEXT_DESTROY); michael@0: } michael@0: michael@0: JS_DestroyContextNoGC(mContext); michael@0: mContext = nullptr; michael@0: DropJSObjects(this); michael@0: } michael@0: michael@0: // QueryInterface implementation for nsJSContext michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext) michael@0: NS_ASSERTION(!tmp->mContext || !js::ContextHasOutstandingRequests(tmp->mContext), michael@0: "Trying to unlink a context with outstanding requests."); michael@0: tmp->mIsInitialized = false; michael@0: tmp->mGCOnDestruction = false; michael@0: tmp->mWindowProxy = nullptr; michael@0: tmp->DestroyJSContext(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSContext) michael@0: NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsJSContext, tmp->GetCCRefcnt()) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext) michael@0: NS_INTERFACE_MAP_ENTRY(nsIScriptContext) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext) michael@0: michael@0: nsrefcnt michael@0: nsJSContext::GetCCRefcnt() michael@0: { michael@0: nsrefcnt refcnt = mRefCnt.get(); michael@0: michael@0: // In the (abnormal) case of synchronous cycle-collection, the context may be michael@0: // actively running JS code in which case we must keep it alive by adding an michael@0: // extra refcount. michael@0: if (mContext && js::ContextHasOutstandingRequests(mContext)) { michael@0: refcnt++; michael@0: } michael@0: michael@0: return refcnt; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: AtomIsEventHandlerName(nsIAtom *aName) michael@0: { michael@0: const char16_t *name = aName->GetUTF16String(); michael@0: michael@0: const char16_t *cp; michael@0: char16_t c; michael@0: for (cp = name; *cp != '\0'; ++cp) michael@0: { michael@0: c = *cp; michael@0: if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: nsIScriptGlobalObject * michael@0: nsJSContext::GetGlobalObject() michael@0: { michael@0: AutoJSContext cx; michael@0: JS::Rooted global(mContext, GetWindowProxy()); michael@0: if (!global) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mGlobalObjectRef) michael@0: return mGlobalObjectRef; michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: JSObject *inner = JS_ObjectToInnerObject(cx, global); michael@0: michael@0: // If this assertion hits then it means that we have a window object as michael@0: // our global, but we never called CreateOuterObject. michael@0: NS_ASSERTION(inner == global, "Shouldn't be able to innerize here"); michael@0: } michael@0: #endif michael@0: michael@0: const JSClass *c = JS_GetClass(global); michael@0: michael@0: nsCOMPtr sgo; michael@0: if (IsDOMClass(c)) { michael@0: sgo = do_QueryInterface(UnwrapDOMObjectToISupports(global)); michael@0: } else { michael@0: if ((~c->flags) & (JSCLASS_HAS_PRIVATE | michael@0: JSCLASS_PRIVATE_IS_NSISUPPORTS)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsISupports *priv = static_cast(js::GetObjectPrivate(global)); michael@0: michael@0: nsCOMPtr wrapped_native = michael@0: do_QueryInterface(priv); michael@0: if (wrapped_native) { michael@0: // The global object is a XPConnect wrapped native, the native in michael@0: // the wrapper might be the nsIScriptGlobalObject michael@0: michael@0: sgo = do_QueryWrappedNative(wrapped_native); michael@0: } else { michael@0: sgo = do_QueryInterface(priv); michael@0: } michael@0: } michael@0: michael@0: // This'll return a pointer to something we're about to release, but michael@0: // that's ok, the JS object will hold it alive long enough. michael@0: return sgo; michael@0: } michael@0: michael@0: JSContext* michael@0: nsJSContext::GetNativeContext() michael@0: { michael@0: return mContext; michael@0: } michael@0: michael@0: nsresult michael@0: nsJSContext::InitContext() michael@0: { michael@0: // Make sure callers of this use michael@0: // WillInitializeContext/DidInitializeContext around this call. michael@0: NS_ENSURE_TRUE(!mIsInitialized, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: if (!mContext) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: ::JS_SetErrorReporter(mContext, NS_ScriptErrorReporter); michael@0: michael@0: JSOptionChangedCallback(js_options_dot_str, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJSContext::InitializeExternalClasses() michael@0: { michael@0: nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager(); michael@0: NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: return nameSpaceManager->InitForContext(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsJSContext::SetProperty(JS::Handle aTarget, const char* aPropName, nsISupports* aArgs) michael@0: { michael@0: nsCxPusher pusher; michael@0: pusher.Push(mContext); michael@0: michael@0: JS::AutoValueVector args(mContext); michael@0: michael@0: JS::Rooted global(mContext, GetWindowProxy()); michael@0: nsresult rv = michael@0: ConvertSupportsTojsvals(aArgs, global, args); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // got the arguments, now attach them. michael@0: michael@0: for (uint32_t i = 0; i < args.length(); ++i) { michael@0: if (!JS_WrapValue(mContext, args.handleAt(i))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted array(mContext, ::JS_NewArrayObject(mContext, args)); michael@0: if (!array) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return JS_DefineProperty(mContext, aTarget, aPropName, array, 0) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsJSContext::ConvertSupportsTojsvals(nsISupports* aArgs, michael@0: JS::Handle aScope, michael@0: JS::AutoValueVector& aArgsOut) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // If the array implements nsIJSArgArray, copy the contents and return. michael@0: nsCOMPtr fastArray = do_QueryInterface(aArgs); michael@0: if (fastArray) { michael@0: uint32_t argc; michael@0: JS::Value* argv; michael@0: rv = fastArray->GetArgs(&argc, reinterpret_cast(&argv)); michael@0: if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // Take the slower path converting each item. michael@0: // Handle only nsIArray and nsIVariant. nsIArray is only needed for michael@0: // SetProperty('arguments', ...); michael@0: michael@0: nsIXPConnect *xpc = nsContentUtils::XPConnect(); michael@0: NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); michael@0: AutoJSContext cx; michael@0: michael@0: if (!aArgs) michael@0: return NS_OK; michael@0: uint32_t argCount; michael@0: // This general purpose function may need to convert an arg array michael@0: // (window.arguments, event-handler args) and a generic property. michael@0: nsCOMPtr argsArray(do_QueryInterface(aArgs)); michael@0: michael@0: if (argsArray) { michael@0: rv = argsArray->GetLength(&argCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (argCount == 0) michael@0: return NS_OK; michael@0: } else { michael@0: argCount = 1; // the nsISupports which is not an array michael@0: } michael@0: michael@0: // Use the caller's auto guards to release and unroot. michael@0: if (!aArgsOut.resize(argCount)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (argsArray) { michael@0: for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) { michael@0: nsCOMPtr arg; michael@0: JS::MutableHandle thisVal = aArgsOut.handleAt(argCtr); michael@0: argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports), michael@0: getter_AddRefs(arg)); michael@0: if (!arg) { michael@0: thisVal.setNull(); michael@0: continue; michael@0: } michael@0: nsCOMPtr variant(do_QueryInterface(arg)); michael@0: if (variant != nullptr) { michael@0: rv = xpc->VariantToJS(cx, aScope, variant, thisVal); michael@0: } else { michael@0: // And finally, support the nsISupportsPrimitives supplied michael@0: // by the AppShell. It generally will pass only strings, but michael@0: // as we have code for handling all, we may as well use it. michael@0: rv = AddSupportsPrimitiveTojsvals(arg, thisVal.address()); michael@0: if (rv == NS_ERROR_NO_INTERFACE) { michael@0: // something else - probably an event object or similar - michael@0: // just wrap it. michael@0: #ifdef DEBUG michael@0: // but first, check its not another nsISupportsPrimitive, as michael@0: // these are now deprecated for use with script contexts. michael@0: nsCOMPtr prim(do_QueryInterface(arg)); michael@0: NS_ASSERTION(prim == nullptr, michael@0: "Don't pass nsISupportsPrimitives - use nsIVariant!"); michael@0: #endif michael@0: JSAutoCompartment ac(cx, aScope); michael@0: rv = nsContentUtils::WrapNative(cx, arg, thisVal); michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: nsCOMPtr variant = do_QueryInterface(aArgs); michael@0: if (variant) { michael@0: rv = xpc->VariantToJS(cx, aScope, variant, aArgsOut.handleAt(0)); michael@0: } else { michael@0: NS_ERROR("Not an array, not an interface?"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: // This really should go into xpconnect somewhere... michael@0: nsresult michael@0: nsJSContext::AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv) michael@0: { michael@0: NS_PRECONDITION(aArg, "Empty arg"); michael@0: michael@0: nsCOMPtr argPrimitive(do_QueryInterface(aArg)); michael@0: if (!argPrimitive) michael@0: return NS_ERROR_NO_INTERFACE; michael@0: michael@0: AutoJSContext cx; michael@0: uint16_t type; michael@0: argPrimitive->GetType(&type); michael@0: michael@0: switch(type) { michael@0: case nsISupportsPrimitive::TYPE_CSTRING : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsAutoCString data; michael@0: michael@0: p->GetData(data); michael@0: michael@0: michael@0: JSString *str = ::JS_NewStringCopyN(cx, data.get(), data.Length()); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *aArgv = STRING_TO_JSVAL(str); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_STRING : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsAutoString data; michael@0: michael@0: p->GetData(data); michael@0: michael@0: // cast is probably safe since wchar_t and jschar are expected michael@0: // to be equivalent; both unsigned 16-bit entities michael@0: JSString *str = michael@0: ::JS_NewUCStringCopyN(cx, data.get(), data.Length()); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *aArgv = STRING_TO_JSVAL(str); michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRBOOL : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: bool data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = BOOLEAN_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRUINT8 : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint8_t data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = INT_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRUINT16 : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint16_t data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = INT_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRUINT32 : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint32_t data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = INT_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_CHAR : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: char data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: JSString *str = ::JS_NewStringCopyN(cx, &data, 1); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: *aArgv = STRING_TO_JSVAL(str); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRINT16 : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: int16_t data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = INT_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_PRINT32 : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: int32_t data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = INT_TO_JSVAL(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_FLOAT : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: float data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = ::JS_NumberValue(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_DOUBLE : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: double data; michael@0: michael@0: p->GetData(&data); michael@0: michael@0: *aArgv = ::JS_NumberValue(data); michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_INTERFACE_POINTER : { michael@0: nsCOMPtr p(do_QueryInterface(argPrimitive)); michael@0: NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr data; michael@0: nsIID *iid = nullptr; michael@0: michael@0: p->GetData(getter_AddRefs(data)); michael@0: p->GetDataIID(&iid); michael@0: NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED); michael@0: michael@0: AutoFree iidGuard(iid); // Free iid upon destruction. michael@0: michael@0: JS::Rooted scope(cx, GetWindowProxy()); michael@0: JS::Rooted v(cx); michael@0: JSAutoCompartment ac(cx, scope); michael@0: nsresult rv = nsContentUtils::WrapNative(cx, data, iid, &v); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aArgv = v; michael@0: michael@0: break; michael@0: } michael@0: case nsISupportsPrimitive::TYPE_ID : michael@0: case nsISupportsPrimitive::TYPE_PRUINT64 : michael@0: case nsISupportsPrimitive::TYPE_PRINT64 : michael@0: case nsISupportsPrimitive::TYPE_PRTIME : michael@0: case nsISupportsPrimitive::TYPE_VOID : { michael@0: NS_WARNING("Unsupported primitive type used"); michael@0: *aArgv = JSVAL_NULL; michael@0: break; michael@0: } michael@0: default : { michael@0: NS_WARNING("Unknown primitive type used"); michael@0: *aArgv = JSVAL_NULL; michael@0: break; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef NS_TRACE_MALLOC michael@0: michael@0: #include // XXX assume Linux if NS_TRACE_MALLOC michael@0: #include michael@0: #ifdef XP_UNIX michael@0: #include michael@0: #endif michael@0: #ifdef XP_WIN32 michael@0: #include michael@0: #endif michael@0: #include "nsTraceMalloc.h" michael@0: michael@0: static bool michael@0: CheckUniversalXPConnectForTraceMalloc(JSContext *cx) michael@0: { michael@0: if (nsContentUtils::IsCallerChrome()) michael@0: return true; michael@0: JS_ReportError(cx, "trace-malloc functions require UniversalXPConnect"); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocDisable(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: NS_TraceMallocDisable(); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocEnable(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: NS_TraceMallocEnable(); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocOpenLogFile(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: int fd; michael@0: if (argc == 0) { michael@0: fd = -1; michael@0: } else { michael@0: JSString *str = JS::ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString filename(cx, str); michael@0: if (!filename) michael@0: return false; michael@0: fd = open(filename.ptr(), O_CREAT | O_WRONLY | O_TRUNC, 0644); michael@0: if (fd < 0) { michael@0: JS_ReportError(cx, "can't open %s: %s", filename.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: } michael@0: args.rval().setInt32(fd); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocChangeLogFD(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: int32_t fd, oldfd; michael@0: if (args.length() == 0) { michael@0: oldfd = -1; michael@0: } else { michael@0: if (!JS::ToInt32(cx, args[0], &fd)) michael@0: return false; michael@0: oldfd = NS_TraceMallocChangeLogFD(fd); michael@0: if (oldfd == -2) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: args.rval().setInt32(oldfd); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocCloseLogFD(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: int32_t fd; michael@0: if (args.length() == 0) { michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: if (!JS::ToInt32(cx, args[0], &fd)) michael@0: return false; michael@0: NS_TraceMallocCloseLogFD((int) fd); michael@0: args.rval().setInt32(fd); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocLogTimestamp(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: JSString *str = JS::ToString(cx, args.get(0)); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString caption(cx, str); michael@0: if (!caption) michael@0: return false; michael@0: NS_TraceMallocLogTimestamp(caption.ptr()); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: TraceMallocDumpAllocations(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: if (!CheckUniversalXPConnectForTraceMalloc(cx)) michael@0: return false; michael@0: michael@0: JSString *str = JS::ToString(cx, args.get(0)); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString pathname(cx, str); michael@0: if (!pathname) michael@0: return false; michael@0: if (NS_TraceMallocDumpAllocations(pathname.ptr()) < 0) { michael@0: JS_ReportError(cx, "can't dump to %s: %s", pathname.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec TraceMallocFunctions[] = { michael@0: JS_FS("TraceMallocDisable", TraceMallocDisable, 0, 0), michael@0: JS_FS("TraceMallocEnable", TraceMallocEnable, 0, 0), michael@0: JS_FS("TraceMallocOpenLogFile", TraceMallocOpenLogFile, 1, 0), michael@0: JS_FS("TraceMallocChangeLogFD", TraceMallocChangeLogFD, 1, 0), michael@0: JS_FS("TraceMallocCloseLogFD", TraceMallocCloseLogFD, 1, 0), michael@0: JS_FS("TraceMallocLogTimestamp", TraceMallocLogTimestamp, 1, 0), michael@0: JS_FS("TraceMallocDumpAllocations", TraceMallocDumpAllocations, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: #endif /* NS_TRACE_MALLOC */ michael@0: michael@0: #ifdef MOZ_DMD michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace dmd { michael@0: michael@0: // See https://wiki.mozilla.org/Performance/MemShrink/DMD for instructions on michael@0: // how to use DMD. michael@0: michael@0: static bool michael@0: ReportAndDump(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JSString *str = JS::ToString(cx, args.get(0)); michael@0: if (!str) michael@0: return false; michael@0: JSAutoByteString pathname(cx, str); michael@0: if (!pathname) michael@0: return false; michael@0: michael@0: FILE* fp = fopen(pathname.ptr(), "w"); michael@0: if (!fp) { michael@0: JS_ReportError(cx, "DMD can't open %s: %s", michael@0: pathname.ptr(), strerror(errno)); michael@0: return false; michael@0: } michael@0: michael@0: dmd::ClearReports(); michael@0: fprintf(stderr, "DMD: running reporters...\n"); michael@0: dmd::RunReportersForThisProcess(); michael@0: dmd::Writer writer(FpWrite, fp); michael@0: dmd::Dump(writer); michael@0: michael@0: fclose(fp); michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: } // namespace dmd michael@0: } // namespace mozilla michael@0: michael@0: static const JSFunctionSpec DMDFunctions[] = { michael@0: JS_FS("DMDReportAndDump", dmd::ReportAndDump, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: #endif // defined(MOZ_DMD) michael@0: michael@0: #ifdef MOZ_JPROF michael@0: michael@0: #include michael@0: michael@0: inline bool michael@0: IsJProfAction(struct sigaction *action) michael@0: { michael@0: return (action->sa_sigaction && michael@0: (action->sa_flags & (SA_RESTART | SA_SIGINFO)) == (SA_RESTART | SA_SIGINFO)); michael@0: } michael@0: michael@0: void NS_JProfStartProfiling(); michael@0: void NS_JProfStopProfiling(); michael@0: void NS_JProfClearCircular(); michael@0: michael@0: static bool michael@0: JProfStartProfilingJS(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: NS_JProfStartProfiling(); michael@0: return true; michael@0: } michael@0: michael@0: void NS_JProfStartProfiling() michael@0: { michael@0: // Figure out whether we're dealing with SIGPROF, SIGALRM, or michael@0: // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for michael@0: // JP_RTC_HZ) michael@0: struct sigaction action; michael@0: michael@0: // Must check ALRM before PROF since both are enabled for real-time michael@0: sigaction(SIGALRM, nullptr, &action); michael@0: //printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); michael@0: if (IsJProfAction(&action)) { michael@0: //printf("Beginning real-time jprof profiling.\n"); michael@0: raise(SIGALRM); michael@0: return; michael@0: } michael@0: michael@0: sigaction(SIGPROF, nullptr, &action); michael@0: //printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); michael@0: if (IsJProfAction(&action)) { michael@0: //printf("Beginning process-time jprof profiling.\n"); michael@0: raise(SIGPROF); michael@0: return; michael@0: } michael@0: michael@0: sigaction(SIGPOLL, nullptr, &action); michael@0: //printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); michael@0: if (IsJProfAction(&action)) { michael@0: //printf("Beginning rtc-based jprof profiling.\n"); michael@0: raise(SIGPOLL); michael@0: return; michael@0: } michael@0: michael@0: printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n"); michael@0: } michael@0: michael@0: static bool michael@0: JProfStopProfilingJS(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: NS_JProfStopProfiling(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: NS_JProfStopProfiling() michael@0: { michael@0: raise(SIGUSR1); michael@0: //printf("Stopped jprof profiling.\n"); michael@0: } michael@0: michael@0: static bool michael@0: JProfClearCircularJS(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: NS_JProfClearCircular(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: NS_JProfClearCircular() michael@0: { michael@0: raise(SIGUSR2); michael@0: //printf("cleared jprof buffer\n"); michael@0: } michael@0: michael@0: static bool michael@0: JProfSaveCircularJS(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: // Not ideal... michael@0: NS_JProfStopProfiling(); michael@0: NS_JProfStartProfiling(); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec JProfFunctions[] = { michael@0: JS_FS("JProfStartProfiling", JProfStartProfilingJS, 0, 0), michael@0: JS_FS("JProfStopProfiling", JProfStopProfilingJS, 0, 0), michael@0: JS_FS("JProfClearCircular", JProfClearCircularJS, 0, 0), michael@0: JS_FS("JProfSaveCircular", JProfSaveCircularJS, 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: #endif /* defined(MOZ_JPROF) */ michael@0: michael@0: nsresult michael@0: nsJSContext::InitClasses(JS::Handle aGlobalObj) michael@0: { michael@0: nsresult rv = InitializeExternalClasses(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: JSOptionChangedCallback(js_options_dot_str, this); michael@0: AutoPushJSContext cx(mContext); michael@0: michael@0: // Attempt to initialize profiling functions michael@0: ::JS_DefineProfilingFunctions(cx, aGlobalObj); michael@0: michael@0: #ifdef NS_TRACE_MALLOC michael@0: if (nsContentUtils::IsCallerChrome()) { michael@0: // Attempt to initialize TraceMalloc functions michael@0: ::JS_DefineFunctions(cx, aGlobalObj, TraceMallocFunctions); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_DMD michael@0: // Attempt to initialize DMD functions michael@0: ::JS_DefineFunctions(cx, aGlobalObj, DMDFunctions); michael@0: #endif michael@0: michael@0: #ifdef MOZ_JPROF michael@0: // Attempt to initialize JProf functions michael@0: ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions); michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsJSContext::WillInitializeContext() michael@0: { michael@0: mIsInitialized = false; michael@0: } michael@0: michael@0: void michael@0: nsJSContext::DidInitializeContext() michael@0: { michael@0: mIsInitialized = true; michael@0: } michael@0: michael@0: bool michael@0: nsJSContext::IsContextInitialized() michael@0: { michael@0: return mIsInitialized; michael@0: } michael@0: michael@0: bool michael@0: nsJSContext::GetProcessingScriptTag() michael@0: { michael@0: return mProcessingScriptTag; michael@0: } michael@0: michael@0: void michael@0: nsJSContext::SetProcessingScriptTag(bool aFlag) michael@0: { michael@0: mProcessingScriptTag = aFlag; michael@0: } michael@0: michael@0: void michael@0: FullGCTimerFired(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: nsJSContext::KillFullGCTimer(); michael@0: uintptr_t reason = reinterpret_cast(aClosure); michael@0: nsJSContext::GarbageCollectNow(static_cast(reason), michael@0: nsJSContext::IncrementalGC); michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason, michael@0: IsIncremental aIncremental, michael@0: IsCompartment aCompartment, michael@0: IsShrinking aShrinking, michael@0: int64_t aSliceMillis) michael@0: { michael@0: PROFILER_LABEL("GC", "GarbageCollectNow"); michael@0: michael@0: MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC); michael@0: michael@0: KillGCTimer(); michael@0: KillShrinkGCBuffersTimer(); michael@0: michael@0: // Reset sPendingLoadCount in case the timer that fired was a michael@0: // timer we scheduled due to a normal GC timer firing while michael@0: // documents were loading. If this happens we're waiting for a michael@0: // document that is taking a long time to load, and we effectively michael@0: // ignore the fact that the currently loading documents are still michael@0: // loading and move on as if they weren't. michael@0: sPendingLoadCount = 0; michael@0: sLoadingInProgress = false; michael@0: michael@0: if (!nsContentUtils::XPConnect() || !sRuntime) { michael@0: return; michael@0: } michael@0: michael@0: if (sCCLockedOut && aIncremental == IncrementalGC) { michael@0: // We're in the middle of incremental GC. Do another slice. michael@0: JS::PrepareForIncrementalGC(sRuntime); michael@0: JS::IncrementalGC(sRuntime, aReason, aSliceMillis); michael@0: return; michael@0: } michael@0: michael@0: JS::PrepareForFullGC(sRuntime); michael@0: if (aIncremental == IncrementalGC) { michael@0: MOZ_ASSERT(aShrinking == NonShrinkingGC); michael@0: JS::IncrementalGC(sRuntime, aReason, aSliceMillis); michael@0: } else if (aShrinking == ShrinkingGC) { michael@0: JS::ShrinkingGC(sRuntime, aReason); michael@0: } else { michael@0: JS::GCForReason(sRuntime, aReason); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::ShrinkGCBuffersNow() michael@0: { michael@0: PROFILER_LABEL("GC", "ShrinkGCBuffersNow"); michael@0: michael@0: KillShrinkGCBuffersTimer(); michael@0: michael@0: JS::ShrinkGCBuffers(sRuntime); michael@0: } michael@0: michael@0: static void michael@0: FinishAnyIncrementalGC() michael@0: { michael@0: if (sCCLockedOut) { michael@0: // We're in the middle of an incremental GC, so finish it. michael@0: JS::PrepareForIncrementalGC(sRuntime); michael@0: JS::FinishIncrementalGC(sRuntime, JS::gcreason::CC_FORCED); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless) michael@0: { michael@0: PRTime startTime = PR_Now(); michael@0: FinishAnyIncrementalGC(); michael@0: bool earlyForgetSkippable = michael@0: sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS; michael@0: nsCycleCollector_forgetSkippable(aRemoveChildless, earlyForgetSkippable); michael@0: sPreviousSuspectedCount = nsCycleCollector_suspectedCount(); michael@0: ++sCleanupsSinceLastGC; michael@0: PRTime delta = PR_Now() - startTime; michael@0: if (sMinForgetSkippableTime > delta) { michael@0: sMinForgetSkippableTime = delta; michael@0: } michael@0: if (sMaxForgetSkippableTime < delta) { michael@0: sMaxForgetSkippableTime = delta; michael@0: } michael@0: sTotalForgetSkippableTime += delta; michael@0: sRemovedPurples += (aSuspected - sPreviousSuspectedCount); michael@0: ++sForgetSkippableBeforeCC; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: static uint32_t michael@0: TimeBetween(TimeStamp start, TimeStamp end) michael@0: { michael@0: MOZ_ASSERT(end >= start); michael@0: return (uint32_t) ((end - start).ToMilliseconds()); michael@0: } michael@0: michael@0: static uint32_t michael@0: TimeUntilNow(TimeStamp start) michael@0: { michael@0: if (start.IsNull()) { michael@0: return 0; michael@0: } michael@0: return TimeBetween(start, TimeStamp::Now()); michael@0: } michael@0: michael@0: struct CycleCollectorStats michael@0: { michael@0: void Clear() michael@0: { michael@0: mBeginSliceTime = TimeStamp(); michael@0: mEndSliceTime = TimeStamp(); michael@0: mBeginTime = TimeStamp(); michael@0: mMaxGCDuration = 0; michael@0: mRanSyncForgetSkippable = false; michael@0: mSuspected = 0; michael@0: mMaxSkippableDuration = 0; michael@0: mMaxSliceTime = 0; michael@0: mTotalSliceTime = 0; michael@0: mAnyLockedOut = false; michael@0: mExtraForgetSkippableCalls = 0; michael@0: } michael@0: michael@0: void PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls = 0); michael@0: michael@0: void FinishCycleCollectionSlice() michael@0: { michael@0: if (mBeginSliceTime.IsNull()) { michael@0: // We already called this method from EndCycleCollectionCallback for this slice. michael@0: return; michael@0: } michael@0: michael@0: mEndSliceTime = TimeStamp::Now(); michael@0: uint32_t sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime); michael@0: mMaxSliceTime = std::max(mMaxSliceTime, sliceTime); michael@0: mTotalSliceTime += sliceTime; michael@0: mBeginSliceTime = TimeStamp(); michael@0: MOZ_ASSERT(mExtraForgetSkippableCalls == 0, "Forget to reset extra forget skippable calls?"); michael@0: } michael@0: michael@0: void RunForgetSkippable(); michael@0: michael@0: // Time the current slice began, including any GC finishing. michael@0: TimeStamp mBeginSliceTime; michael@0: michael@0: // Time the previous slice of the current CC ended. michael@0: TimeStamp mEndSliceTime; michael@0: michael@0: // Time the current cycle collection began. michael@0: TimeStamp mBeginTime; michael@0: michael@0: // The longest GC finishing duration for any slice of the current CC. michael@0: uint32_t mMaxGCDuration; michael@0: michael@0: // True if we ran sync forget skippable in any slice of the current CC. michael@0: bool mRanSyncForgetSkippable; michael@0: michael@0: // Number of suspected objects at the start of the current CC. michael@0: uint32_t mSuspected; michael@0: michael@0: // The longest duration spent on sync forget skippable in any slice of the michael@0: // current CC. michael@0: uint32_t mMaxSkippableDuration; michael@0: michael@0: // The longest pause of any slice in the current CC. michael@0: uint32_t mMaxSliceTime; michael@0: michael@0: // The total amount of time spent actually running the current CC. michael@0: uint32_t mTotalSliceTime; michael@0: michael@0: // True if we were locked out by the GC in any slice of the current CC. michael@0: bool mAnyLockedOut; michael@0: michael@0: int32_t mExtraForgetSkippableCalls; michael@0: }; michael@0: michael@0: CycleCollectorStats gCCStats; michael@0: michael@0: void michael@0: CycleCollectorStats::PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls) michael@0: { michael@0: mBeginSliceTime = TimeStamp::Now(); michael@0: michael@0: // Before we begin the cycle collection, make sure there is no active GC. michael@0: if (sCCLockedOut) { michael@0: mAnyLockedOut = true; michael@0: FinishAnyIncrementalGC(); michael@0: uint32_t gcTime = TimeBetween(mBeginSliceTime, TimeStamp::Now()); michael@0: mMaxGCDuration = std::max(mMaxGCDuration, gcTime); michael@0: } michael@0: michael@0: mExtraForgetSkippableCalls = aExtraForgetSkippableCalls; michael@0: } michael@0: michael@0: void michael@0: CycleCollectorStats::RunForgetSkippable() michael@0: { michael@0: // Run forgetSkippable synchronously to reduce the size of the CC graph. This michael@0: // is particularly useful if we recently finished a GC. michael@0: if (mExtraForgetSkippableCalls >= 0) { michael@0: TimeStamp beginForgetSkippable = TimeStamp::Now(); michael@0: bool ranSyncForgetSkippable = false; michael@0: while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) { michael@0: FireForgetSkippable(nsCycleCollector_suspectedCount(), false); michael@0: ranSyncForgetSkippable = true; michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mExtraForgetSkippableCalls; ++i) { michael@0: FireForgetSkippable(nsCycleCollector_suspectedCount(), false); michael@0: ranSyncForgetSkippable = true; michael@0: } michael@0: michael@0: if (ranSyncForgetSkippable) { michael@0: mMaxSkippableDuration = michael@0: std::max(mMaxSkippableDuration, TimeUntilNow(beginForgetSkippable)); michael@0: mRanSyncForgetSkippable = true; michael@0: } michael@0: michael@0: } michael@0: mExtraForgetSkippableCalls = 0; michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, michael@0: int32_t aExtraForgetSkippableCalls) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: return; michael@0: } michael@0: michael@0: PROFILER_LABEL("CC", "CycleCollectNow"); michael@0: gCCStats.PrepareForCycleCollectionSlice(aExtraForgetSkippableCalls); michael@0: nsCycleCollector_collect(aListener); michael@0: gCCStats.FinishCycleCollectionSlice(); michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::RunCycleCollectorSlice() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: return; michael@0: } michael@0: michael@0: PROFILER_LABEL("CC", "RunCycleCollectorSlice"); michael@0: michael@0: gCCStats.PrepareForCycleCollectionSlice(); michael@0: michael@0: // Decide how long we want to budget for this slice. By default, michael@0: // use an unlimited budget. michael@0: int64_t sliceBudget = -1; michael@0: michael@0: if (sIncrementalCC) { michael@0: if (gCCStats.mBeginTime.IsNull()) { michael@0: // If no CC is in progress, use the standard slice time. michael@0: sliceBudget = kICCSliceBudget; michael@0: } else { michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: // Only run a limited slice if we're within the max running time. michael@0: if (TimeBetween(gCCStats.mBeginTime, now) < kMaxICCDuration) { michael@0: float sliceMultiplier = std::max(TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay, 1.0f); michael@0: sliceBudget = kICCSliceBudget * sliceMultiplier; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCycleCollector_collectSlice(sliceBudget); michael@0: michael@0: gCCStats.FinishCycleCollectionSlice(); michael@0: } michael@0: michael@0: static void michael@0: ICCTimerFired(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: if (sDidShutdown) { michael@0: return; michael@0: } michael@0: michael@0: // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us michael@0: // to synchronously finish the GC, which is bad. michael@0: michael@0: if (sCCLockedOut) { michael@0: PRTime now = PR_Now(); michael@0: if (sCCLockedOutTime == 0) { michael@0: sCCLockedOutTime = now; michael@0: return; michael@0: } michael@0: if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsJSContext::RunCycleCollectorSlice(); michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::BeginCycleCollectionCallback() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime; michael@0: gCCStats.mSuspected = nsCycleCollector_suspectedCount(); michael@0: michael@0: KillCCTimer(); michael@0: michael@0: gCCStats.RunForgetSkippable(); michael@0: michael@0: MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed."); michael@0: michael@0: if (!sIncrementalCC) { michael@0: return; michael@0: } michael@0: michael@0: CallCreateInstance("@mozilla.org/timer;1", &sICCTimer); michael@0: if (sICCTimer) { michael@0: sICCTimer->InitWithFuncCallback(ICCTimerFired, michael@0: nullptr, michael@0: kICCIntersliceDelay, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsJSContext::KillICCTimer(); michael@0: michael@0: // Update timing information for the current slice before we log it, if michael@0: // we previously called PrepareForCycleCollectionSlice(). During shutdown michael@0: // CCs, this won't happen. michael@0: gCCStats.FinishCycleCollectionSlice(); michael@0: michael@0: sCCollectedWaitingForGC += aResults.mFreedRefCounted + aResults.mFreedGCed; michael@0: michael@0: if (NeedsGCAfterCC()) { michael@0: PokeGC(JS::gcreason::CC_WAITING); michael@0: } michael@0: michael@0: TimeStamp endCCTimeStamp = TimeStamp::Now(); michael@0: michael@0: PRTime endCCTime; michael@0: if (sPostGCEventsToObserver) { michael@0: endCCTime = PR_Now(); michael@0: } michael@0: michael@0: // Log information about the CC via telemetry, JSON and the console. michael@0: uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, gCCStats.mMaxSliceTime); michael@0: michael@0: if (!sLastCCEndTime.IsNull()) { michael@0: // TimeBetween returns milliseconds, but we want to report seconds. michael@0: uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime) / 1000; michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween); michael@0: } michael@0: sLastCCEndTime = endCCTimeStamp; michael@0: michael@0: Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX, michael@0: sMaxForgetSkippableTime / PR_USEC_PER_MSEC); michael@0: michael@0: PRTime delta = GetCollectionTimeDelta(); michael@0: michael@0: uint32_t cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1; michael@0: uint32_t minForgetSkippableTime = (sMinForgetSkippableTime == UINT32_MAX) michael@0: ? 0 : sMinForgetSkippableTime; michael@0: michael@0: if (sPostGCEventsToConsole) { michael@0: nsCString mergeMsg; michael@0: if (aResults.mMergedZones) { michael@0: mergeMsg.AssignLiteral(" merged"); michael@0: } michael@0: michael@0: nsCString gcMsg; michael@0: if (aResults.mForcedGC) { michael@0: gcMsg.AssignLiteral(", forced a GC"); michael@0: } michael@0: michael@0: NS_NAMED_MULTILINE_LITERAL_STRING(kFmt, michael@0: MOZ_UTF16("CC(T+%.1f) max pause: %lums, total time: %lums, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu waiting for GC)%s\n") michael@0: MOZ_UTF16("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu")); michael@0: nsString msg; michael@0: msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC, michael@0: gCCStats.mMaxSliceTime, gCCStats.mTotalSliceTime, michael@0: gCCStats.mSuspected, michael@0: aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(), michael@0: aResults.mFreedRefCounted, aResults.mFreedGCed, michael@0: sCCollectedWaitingForGC, sLikelyShortLivingObjectsNeedingGC, michael@0: gcMsg.get(), michael@0: sForgetSkippableBeforeCC, michael@0: minForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: sMaxForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: (sTotalForgetSkippableTime / cleanups) / michael@0: PR_USEC_PER_MSEC, michael@0: sTotalForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: gCCStats.mMaxSkippableDuration, sRemovedPurples)); michael@0: nsCOMPtr cs = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (cs) { michael@0: cs->LogStringMessage(msg.get()); michael@0: } michael@0: } michael@0: michael@0: if (sPostGCEventsToObserver) { michael@0: NS_NAMED_MULTILINE_LITERAL_STRING(kJSONFmt, michael@0: MOZ_UTF16("{ \"timestamp\": %llu, ") michael@0: MOZ_UTF16("\"duration\": %lu, ") michael@0: MOZ_UTF16("\"max_slice_pause\": %lu, ") michael@0: MOZ_UTF16("\"total_slice_pause\": %lu, ") michael@0: MOZ_UTF16("\"max_finish_gc_duration\": %lu, ") michael@0: MOZ_UTF16("\"max_sync_skippable_duration\": %lu, ") michael@0: MOZ_UTF16("\"suspected\": %lu, ") michael@0: MOZ_UTF16("\"visited\": { ") michael@0: MOZ_UTF16("\"RCed\": %lu, ") michael@0: MOZ_UTF16("\"GCed\": %lu }, ") michael@0: MOZ_UTF16("\"collected\": { ") michael@0: MOZ_UTF16("\"RCed\": %lu, ") michael@0: MOZ_UTF16("\"GCed\": %lu }, ") michael@0: MOZ_UTF16("\"waiting_for_gc\": %lu, ") michael@0: MOZ_UTF16("\"short_living_objects_waiting_for_gc\": %lu, ") michael@0: MOZ_UTF16("\"forced_gc\": %d, ") michael@0: MOZ_UTF16("\"forget_skippable\": { ") michael@0: MOZ_UTF16("\"times_before_cc\": %lu, ") michael@0: MOZ_UTF16("\"min\": %lu, ") michael@0: MOZ_UTF16("\"max\": %lu, ") michael@0: MOZ_UTF16("\"avg\": %lu, ") michael@0: MOZ_UTF16("\"total\": %lu, ") michael@0: MOZ_UTF16("\"removed\": %lu } ") michael@0: MOZ_UTF16("}")); michael@0: nsString json; michael@0: json.Adopt(nsTextFormatter::smprintf(kJSONFmt.get(), endCCTime, ccNowDuration, michael@0: gCCStats.mMaxSliceTime, michael@0: gCCStats.mTotalSliceTime, michael@0: gCCStats.mMaxGCDuration, michael@0: gCCStats.mMaxSkippableDuration, michael@0: gCCStats.mSuspected, michael@0: aResults.mVisitedRefCounted, aResults.mVisitedGCed, michael@0: aResults.mFreedRefCounted, aResults.mFreedGCed, michael@0: sCCollectedWaitingForGC, michael@0: sLikelyShortLivingObjectsNeedingGC, michael@0: aResults.mForcedGC, michael@0: sForgetSkippableBeforeCC, michael@0: minForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: sMaxForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: (sTotalForgetSkippableTime / cleanups) / michael@0: PR_USEC_PER_MSEC, michael@0: sTotalForgetSkippableTime / PR_USEC_PER_MSEC, michael@0: sRemovedPurples)); michael@0: nsCOMPtr observerService = mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->NotifyObservers(nullptr, "cycle-collection-statistics", json.get()); michael@0: } michael@0: } michael@0: michael@0: // Update global state to indicate we have just run a cycle collection. michael@0: sMinForgetSkippableTime = UINT32_MAX; michael@0: sMaxForgetSkippableTime = 0; michael@0: sTotalForgetSkippableTime = 0; michael@0: sRemovedPurples = 0; michael@0: sForgetSkippableBeforeCC = 0; michael@0: sNeedsFullCC = false; michael@0: sNeedsGCAfterCC = false; michael@0: gCCStats.Clear(); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsJSContext::KillInterSliceGCTimer(); michael@0: nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC, michael@0: nsJSContext::IncrementalGC, michael@0: nsJSContext::CompartmentGC, michael@0: nsJSContext::NonShrinkingGC, michael@0: NS_INTERSLICE_GC_BUDGET); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: GCTimerFired(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsJSContext::KillGCTimer(); michael@0: uintptr_t reason = reinterpret_cast(aClosure); michael@0: nsJSContext::GarbageCollectNow(static_cast(reason), michael@0: nsJSContext::IncrementalGC, michael@0: nsJSContext::CompartmentGC); michael@0: } michael@0: michael@0: void michael@0: ShrinkGCBuffersTimerFired(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsJSContext::KillShrinkGCBuffersTimer(); michael@0: nsJSContext::ShrinkGCBuffersNow(); michael@0: } michael@0: michael@0: static bool michael@0: ShouldTriggerCC(uint32_t aSuspected) michael@0: { michael@0: return sNeedsFullCC || michael@0: aSuspected > NS_CC_PURPLE_LIMIT || michael@0: (aSuspected > NS_CC_FORCED_PURPLE_LIMIT && michael@0: TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED); michael@0: } michael@0: michael@0: static uint32_t michael@0: TimeToNextCC() michael@0: { michael@0: if (sIncrementalCC) { michael@0: return NS_CC_DELAY - kMaxICCDuration; michael@0: } michael@0: return NS_CC_DELAY; michael@0: } michael@0: michael@0: static_assert(NS_CC_DELAY > kMaxICCDuration, "ICC shouldn't reduce CC delay to 0"); michael@0: michael@0: static void michael@0: CCTimerFired(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: if (sDidShutdown) { michael@0: return; michael@0: } michael@0: michael@0: static uint32_t ccDelay = NS_CC_DELAY; michael@0: if (sCCLockedOut) { michael@0: ccDelay = TimeToNextCC() / 3; michael@0: michael@0: PRTime now = PR_Now(); michael@0: if (sCCLockedOutTime == 0) { michael@0: // Reset sCCTimerFireCount so that we run forgetSkippable michael@0: // often enough before CC. Because of reduced ccDelay michael@0: // forgetSkippable will be called just a few times. michael@0: // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling michael@0: // forgetSkippable and CycleCollectNow eventually. michael@0: sCCTimerFireCount = 0; michael@0: sCCLockedOutTime = now; michael@0: return; michael@0: } michael@0: if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: ++sCCTimerFireCount; michael@0: michael@0: // During early timer fires, we only run forgetSkippable. During the first michael@0: // late timer fire, we decide if we are going to have a second and final michael@0: // late timer fire, where we may begin to run the CC. Should run at least one michael@0: // early timer fire to allow cleanup before the CC. michael@0: int32_t numEarlyTimerFires = std::max((int32_t)ccDelay / NS_CC_SKIPPABLE_DELAY - 2, 1); michael@0: bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires; michael@0: uint32_t suspected = nsCycleCollector_suspectedCount(); michael@0: if (isLateTimerFire && ShouldTriggerCC(suspected)) { michael@0: if (sCCTimerFireCount == numEarlyTimerFires + 1) { michael@0: FireForgetSkippable(suspected, true); michael@0: if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { michael@0: // Our efforts to avoid a CC have failed, so we return to let the michael@0: // timer fire once more to trigger a CC. michael@0: return; michael@0: } michael@0: } else { michael@0: // We are in the final timer fire and still meet the conditions for michael@0: // triggering a CC. Let RunCycleCollectorSlice finish the current IGC, if michael@0: // any because that will allow us to include the GC time in the CC pause. michael@0: nsJSContext::RunCycleCollectorSlice(); michael@0: } michael@0: } else if ((sPreviousSuspectedCount + 100) <= suspected) { michael@0: // Only do a forget skippable if there are more than a few new objects. michael@0: FireForgetSkippable(suspected, false); michael@0: } michael@0: michael@0: if (isLateTimerFire) { michael@0: ccDelay = TimeToNextCC(); michael@0: michael@0: // We have either just run the CC or decided we don't want to run the CC michael@0: // next time, so kill the timer. michael@0: sPreviousSuspectedCount = 0; michael@0: nsJSContext::KillCCTimer(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: uint32_t michael@0: nsJSContext::CleanupsSinceLastGC() michael@0: { michael@0: return sCleanupsSinceLastGC; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::LoadStart() michael@0: { michael@0: sLoadingInProgress = true; michael@0: ++sPendingLoadCount; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::LoadEnd() michael@0: { michael@0: if (!sLoadingInProgress) michael@0: return; michael@0: michael@0: // sPendingLoadCount is not a well managed load counter (and doesn't michael@0: // need to be), so make sure we don't make it wrap backwards here. michael@0: if (sPendingLoadCount > 0) { michael@0: --sPendingLoadCount; michael@0: return; michael@0: } michael@0: michael@0: // Its probably a good idea to GC soon since we have finished loading. michael@0: sLoadingInProgress = false; michael@0: PokeGC(JS::gcreason::LOAD_END); michael@0: } michael@0: michael@0: // Only trigger expensive timers when they have been checked a number of times. michael@0: static bool michael@0: ReadyToTriggerExpensiveCollectorTimer() michael@0: { michael@0: bool ready = kPokesBetweenExpensiveCollectorTriggers < ++sExpensiveCollectorPokes; michael@0: if (ready) { michael@0: sExpensiveCollectorPokes = 0; michael@0: } michael@0: return ready; michael@0: } michael@0: michael@0: michael@0: // Check all of the various collector timers and see if they are waiting to fire. michael@0: // For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger michael@0: // the collection occasionally, because they are expensive. The incremental collector michael@0: // timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so michael@0: // always run their corresponding timer. michael@0: michael@0: // This does not check sFullGCTimer, as that's an even more expensive collector we run michael@0: // on a long timer. michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::RunNextCollectorTimer() michael@0: { michael@0: if (sShuttingDown) { michael@0: return; michael@0: } michael@0: michael@0: if (sGCTimer) { michael@0: if (ReadyToTriggerExpensiveCollectorTimer()) { michael@0: GCTimerFired(nullptr, reinterpret_cast(JS::gcreason::DOM_WINDOW_UTILS)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (sInterSliceGCTimer) { michael@0: InterSliceGCTimerFired(nullptr, nullptr); michael@0: return; michael@0: } michael@0: michael@0: // Check the CC timers after the GC timers, because the CC timers won't do michael@0: // anything if a GC is in progress. michael@0: MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out."); michael@0: michael@0: if (sCCTimer) { michael@0: if (ReadyToTriggerExpensiveCollectorTimer()) { michael@0: CCTimerFired(nullptr, nullptr); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (sICCTimer) { michael@0: ICCTimerFired(nullptr, nullptr); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay) michael@0: { michael@0: if (sGCTimer || sInterSliceGCTimer || sShuttingDown) { michael@0: // There's already a timer for GC'ing, just return michael@0: return; michael@0: } michael@0: michael@0: if (sCCTimer) { michael@0: // Make sure CC is called... michael@0: sNeedsFullCC = true; michael@0: // and GC after it. michael@0: sNeedsGCAfterCC = true; michael@0: return; michael@0: } michael@0: michael@0: if (sICCTimer) { michael@0: // Make sure GC is called after the current CC completes. michael@0: // No need to set sNeedsFullCC because we are currently running a CC. michael@0: sNeedsGCAfterCC = true; michael@0: return; michael@0: } michael@0: michael@0: CallCreateInstance("@mozilla.org/timer;1", &sGCTimer); michael@0: michael@0: if (!sGCTimer) { michael@0: // Failed to create timer (probably because we're in XPCOM shutdown) michael@0: return; michael@0: } michael@0: michael@0: static bool first = true; michael@0: michael@0: sGCTimer->InitWithFuncCallback(GCTimerFired, reinterpret_cast(aReason), michael@0: aDelay michael@0: ? aDelay michael@0: : (first michael@0: ? NS_FIRST_GC_DELAY michael@0: : NS_GC_DELAY), michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: first = false; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::PokeShrinkGCBuffers() michael@0: { michael@0: if (sShrinkGCBuffersTimer || sShuttingDown) { michael@0: return; michael@0: } michael@0: michael@0: CallCreateInstance("@mozilla.org/timer;1", &sShrinkGCBuffersTimer); michael@0: michael@0: if (!sShrinkGCBuffersTimer) { michael@0: // Failed to create timer (probably because we're in XPCOM shutdown) michael@0: return; michael@0: } michael@0: michael@0: sShrinkGCBuffersTimer->InitWithFuncCallback(ShrinkGCBuffersTimerFired, nullptr, michael@0: NS_SHRINK_GC_BUFFERS_DELAY, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSContext::MaybePokeCC() michael@0: { michael@0: if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) { michael@0: return; michael@0: } michael@0: michael@0: if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { michael@0: sCCTimerFireCount = 0; michael@0: CallCreateInstance("@mozilla.org/timer;1", &sCCTimer); michael@0: if (!sCCTimer) { michael@0: return; michael@0: } michael@0: // We can kill some objects before running forgetSkippable. michael@0: nsCycleCollector_dispatchDeferredDeletion(); michael@0: michael@0: sCCTimer->InitWithFuncCallback(CCTimerFired, nullptr, michael@0: NS_CC_SKIPPABLE_DELAY, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::KillGCTimer() michael@0: { michael@0: if (sGCTimer) { michael@0: sGCTimer->Cancel(); michael@0: NS_RELEASE(sGCTimer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsJSContext::KillFullGCTimer() michael@0: { michael@0: if (sFullGCTimer) { michael@0: sFullGCTimer->Cancel(); michael@0: NS_RELEASE(sFullGCTimer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsJSContext::KillInterSliceGCTimer() michael@0: { michael@0: if (sInterSliceGCTimer) { michael@0: sInterSliceGCTimer->Cancel(); michael@0: NS_RELEASE(sInterSliceGCTimer); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::KillShrinkGCBuffersTimer() michael@0: { michael@0: if (sShrinkGCBuffersTimer) { michael@0: sShrinkGCBuffersTimer->Cancel(); michael@0: NS_RELEASE(sShrinkGCBuffersTimer); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::KillCCTimer() michael@0: { michael@0: sCCLockedOutTime = 0; michael@0: if (sCCTimer) { michael@0: sCCTimer->Cancel(); michael@0: NS_RELEASE(sCCTimer); michael@0: } michael@0: } michael@0: michael@0: //static michael@0: void michael@0: nsJSContext::KillICCTimer() michael@0: { michael@0: sCCLockedOutTime = 0; michael@0: michael@0: if (sICCTimer) { michael@0: sICCTimer->Cancel(); michael@0: NS_RELEASE(sICCTimer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsJSContext::GC(JS::gcreason::Reason aReason) michael@0: { michael@0: PokeGC(aReason); michael@0: } michael@0: michael@0: class NotifyGCEndRunnable : public nsRunnable michael@0: { michael@0: nsString mMessage; michael@0: michael@0: public: michael@0: NotifyGCEndRunnable(const nsString& aMessage) : mMessage(aMessage) {} michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: NotifyGCEndRunnable::Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr observerService = mozilla::services::GetObserverService(); michael@0: if (!observerService) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: const jschar oomMsg[3] = { '{', '}', 0 }; michael@0: const jschar *toSend = mMessage.get() ? mMessage.get() : oomMsg; michael@0: observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescription &aDesc) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread"); michael@0: michael@0: if (aProgress == JS::GC_CYCLE_END) { michael@0: PRTime delta = GetCollectionTimeDelta(); michael@0: michael@0: if (sPostGCEventsToConsole) { michael@0: NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f) "); michael@0: nsString prefix, gcstats; michael@0: gcstats.Adopt(aDesc.formatMessage(aRt)); michael@0: prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(), michael@0: double(delta) / PR_USEC_PER_SEC)); michael@0: nsString msg = prefix + gcstats; michael@0: nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (cs) { michael@0: cs->LogStringMessage(msg.get()); michael@0: } michael@0: } michael@0: michael@0: if (sPostGCEventsToObserver) { michael@0: nsString json; michael@0: json.Adopt(aDesc.formatJSON(aRt, PR_Now())); michael@0: nsRefPtr notify = new NotifyGCEndRunnable(json); michael@0: NS_DispatchToMainThread(notify); michael@0: } michael@0: } michael@0: michael@0: // Prevent cycle collections and shrinking during incremental GC. michael@0: if (aProgress == JS::GC_CYCLE_BEGIN) { michael@0: sCCLockedOut = true; michael@0: nsJSContext::KillShrinkGCBuffersTimer(); michael@0: } else if (aProgress == JS::GC_CYCLE_END) { michael@0: sCCLockedOut = false; michael@0: } michael@0: michael@0: // The GC has more work to do, so schedule another GC slice. michael@0: if (aProgress == JS::GC_SLICE_END) { michael@0: nsJSContext::KillInterSliceGCTimer(); michael@0: if (!sShuttingDown) { michael@0: CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer); michael@0: sInterSliceGCTimer->InitWithFuncCallback(InterSliceGCTimerFired, michael@0: nullptr, michael@0: NS_INTERSLICE_GC_DELAY, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: if (aProgress == JS::GC_CYCLE_END) { michael@0: // May need to kill the inter-slice GC timer michael@0: nsJSContext::KillInterSliceGCTimer(); michael@0: michael@0: sCCollectedWaitingForGC = 0; michael@0: sLikelyShortLivingObjectsNeedingGC = 0; michael@0: sCleanupsSinceLastGC = 0; michael@0: sNeedsFullCC = true; michael@0: sHasRunGC = true; michael@0: nsJSContext::MaybePokeCC(); michael@0: michael@0: if (aDesc.isCompartment_) { michael@0: if (!sFullGCTimer && !sShuttingDown) { michael@0: CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer); michael@0: JS::gcreason::Reason reason = JS::gcreason::FULL_GC_TIMER; michael@0: sFullGCTimer->InitWithFuncCallback(FullGCTimerFired, michael@0: reinterpret_cast(reason), michael@0: NS_FULL_GC_DELAY, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } else { michael@0: nsJSContext::KillFullGCTimer(); michael@0: michael@0: // Avoid shrinking during heavy activity, which is suggested by michael@0: // compartment GC. michael@0: nsJSContext::PokeShrinkGCBuffers(); michael@0: } michael@0: } michael@0: michael@0: if ((aProgress == JS::GC_SLICE_END || aProgress == JS::GC_CYCLE_END) && michael@0: ShouldTriggerCC(nsCycleCollector_suspectedCount())) { michael@0: nsCycleCollector_dispatchDeferredDeletion(); michael@0: } michael@0: michael@0: if (sPrevGCSliceCallback) michael@0: (*sPrevGCSliceCallback)(aRt, aProgress, aDesc); michael@0: } michael@0: michael@0: void michael@0: nsJSContext::ReportPendingException() michael@0: { michael@0: if (mIsInitialized) { michael@0: nsJSUtils::ReportPendingException(mContext); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsJSContext::SetWindowProxy(JS::Handle aWindowProxy) michael@0: { michael@0: mWindowProxy = aWindowProxy; michael@0: } michael@0: michael@0: JSObject* michael@0: nsJSContext::GetWindowProxy() michael@0: { michael@0: JSObject* windowProxy = GetWindowProxyPreserveColor(); michael@0: if (windowProxy) { michael@0: JS::ExposeObjectToActiveJS(windowProxy); michael@0: } michael@0: michael@0: return windowProxy; michael@0: } michael@0: michael@0: JSObject* michael@0: nsJSContext::GetWindowProxyPreserveColor() michael@0: { michael@0: return mWindowProxy; michael@0: } michael@0: michael@0: void michael@0: nsJSContext::LikelyShortLivingObjectCreated() michael@0: { michael@0: ++sLikelyShortLivingObjectsNeedingGC; michael@0: } michael@0: michael@0: void michael@0: mozilla::dom::StartupJSEnvironment() michael@0: { michael@0: // initialize all our statics, so that we can restart XPCOM michael@0: sGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr; michael@0: sCCLockedOut = false; michael@0: sCCLockedOutTime = 0; michael@0: sLastCCEndTime = TimeStamp(); michael@0: sHasRunGC = false; michael@0: sPendingLoadCount = 0; michael@0: sLoadingInProgress = false; michael@0: sCCollectedWaitingForGC = 0; michael@0: sLikelyShortLivingObjectsNeedingGC = 0; michael@0: sPostGCEventsToConsole = false; michael@0: sNeedsFullCC = false; michael@0: sNeedsGCAfterCC = false; michael@0: gNameSpaceManager = nullptr; michael@0: sRuntimeService = nullptr; michael@0: sRuntime = nullptr; michael@0: sIsInitialized = false; michael@0: sDidShutdown = false; michael@0: sShuttingDown = false; michael@0: sContextCount = 0; michael@0: sSecurityManager = nullptr; michael@0: gCCStats.Clear(); michael@0: sExpensiveCollectorPokes = 0; michael@0: } michael@0: michael@0: static void michael@0: ReportAllJSExceptionsPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: bool reportAll = Preferences::GetBool(aPrefName, false); michael@0: nsContentUtils::XPConnect()->SetReportAllJSExceptions(reportAll); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: int32_t highwatermark = Preferences::GetInt(aPrefName, 128); michael@0: michael@0: JS_SetGCParameter(sRuntime, JSGC_MAX_MALLOC_BYTES, michael@0: highwatermark * 1024L * 1024L); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: int32_t pref = Preferences::GetInt(aPrefName, -1); michael@0: // handle overflow and negative pref values michael@0: uint32_t max = (pref <= 0 || pref >= 0x1000) ? -1 : (uint32_t)pref * 1024 * 1024; michael@0: JS_SetGCParameter(sRuntime, JSGC_MAX_BYTES, max); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryGCModePrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: bool enableCompartmentGC = Preferences::GetBool("javascript.options.mem.gc_per_compartment"); michael@0: bool enableIncrementalGC = Preferences::GetBool("javascript.options.mem.gc_incremental"); michael@0: JSGCMode mode; michael@0: if (enableIncrementalGC) { michael@0: mode = JSGC_MODE_INCREMENTAL; michael@0: } else if (enableCompartmentGC) { michael@0: mode = JSGC_MODE_COMPARTMENT; michael@0: } else { michael@0: mode = JSGC_MODE_GLOBAL; michael@0: } michael@0: JS_SetGCParameter(sRuntime, JSGC_MODE, mode); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: int32_t pref = Preferences::GetInt(aPrefName, -1); michael@0: // handle overflow and negative pref values michael@0: if (pref > 0 && pref < 100000) michael@0: JS_SetGCParameter(sRuntime, JSGC_SLICE_TIME_BUDGET, pref); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryGCPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: int32_t pref = Preferences::GetInt(aPrefName, -1); michael@0: // handle overflow and negative pref values michael@0: if (pref >= 0 && pref < 10000) michael@0: JS_SetGCParameter(sRuntime, (JSGCParamKey)(intptr_t)aClosure, pref); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryGCDynamicHeapGrowthPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: bool pref = Preferences::GetBool(aPrefName); michael@0: JS_SetGCParameter(sRuntime, JSGC_DYNAMIC_HEAP_GROWTH, pref); michael@0: } michael@0: michael@0: static void michael@0: SetMemoryGCDynamicMarkSlicePrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: bool pref = Preferences::GetBool(aPrefName); michael@0: JS_SetGCParameter(sRuntime, JSGC_DYNAMIC_MARK_SLICE, pref); michael@0: } michael@0: michael@0: static void michael@0: SetIncrementalCCPrefChangedCallback(const char* aPrefName, void* aClosure) michael@0: { michael@0: bool pref = Preferences::GetBool(aPrefName); michael@0: sIncrementalCC = pref; michael@0: } michael@0: michael@0: JSObject* michael@0: NS_DOMReadStructuredClone(JSContext* cx, michael@0: JSStructuredCloneReader* reader, michael@0: uint32_t tag, michael@0: uint32_t data, michael@0: void* closure) michael@0: { michael@0: if (tag == SCTAG_DOM_IMAGEDATA) { michael@0: // Read the information out of the stream. michael@0: uint32_t width, height; michael@0: JS::Rooted dataArray(cx); michael@0: if (!JS_ReadUint32Pair(reader, &width, &height) || michael@0: !JS_ReadTypedArray(reader, &dataArray)) { michael@0: return nullptr; michael@0: } michael@0: MOZ_ASSERT(dataArray.isObject()); michael@0: michael@0: // Protect the result from a moving GC in ~nsRefPtr. michael@0: JS::Rooted result(cx); michael@0: { michael@0: // Construct the ImageData. michael@0: nsRefPtr imageData = new ImageData(width, height, michael@0: dataArray.toObject()); michael@0: // Wrap it in a JS::Value. michael@0: result = imageData->WrapObject(cx); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // Don't know what this is. Bail. michael@0: xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: NS_DOMWriteStructuredClone(JSContext* cx, michael@0: JSStructuredCloneWriter* writer, michael@0: JS::Handle obj, michael@0: void *closure) michael@0: { michael@0: ImageData* imageData; michael@0: nsresult rv = UNWRAP_OBJECT(ImageData, obj, imageData); michael@0: if (NS_FAILED(rv)) { michael@0: // Don't know what this is. Bail. michael@0: xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); michael@0: return false; michael@0: } michael@0: michael@0: // Prepare the ImageData internals. michael@0: uint32_t width = imageData->Width(); michael@0: uint32_t height = imageData->Height(); michael@0: JS::Rooted dataArray(cx, imageData->GetDataObject()); michael@0: michael@0: // Write the internals to the stream. michael@0: JSAutoCompartment ac(cx, dataArray); michael@0: JS::Rooted arrayValue(cx, JS::ObjectValue(*dataArray)); michael@0: return JS_WriteUint32Pair(writer, SCTAG_DOM_IMAGEDATA, 0) && michael@0: JS_WriteUint32Pair(writer, width, height) && michael@0: JS_WriteTypedArray(writer, arrayValue); michael@0: } michael@0: michael@0: void michael@0: NS_DOMStructuredCloneError(JSContext* cx, michael@0: uint32_t errorid) michael@0: { michael@0: // We don't currently support any extensions to structured cloning. michael@0: xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); michael@0: } michael@0: michael@0: static bool michael@0: AsmJSCacheOpenEntryForRead(JS::Handle aGlobal, michael@0: const jschar* aBegin, michael@0: const jschar* aLimit, michael@0: size_t* aSize, michael@0: const uint8_t** aMemory, michael@0: intptr_t *aHandle) michael@0: { michael@0: nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal); michael@0: return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory, michael@0: aHandle); michael@0: } michael@0: michael@0: static bool michael@0: AsmJSCacheOpenEntryForWrite(JS::Handle aGlobal, michael@0: bool aInstalled, michael@0: const jschar* aBegin, michael@0: const jschar* aEnd, michael@0: size_t aSize, michael@0: uint8_t** aMemory, michael@0: intptr_t* aHandle) michael@0: { michael@0: nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal); michael@0: return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd, michael@0: aSize, aMemory, aHandle); michael@0: } michael@0: michael@0: static void michael@0: OnLargeAllocationFailure() michael@0: { michael@0: nsCOMPtr os = michael@0: mozilla::services::GetObserverService(); michael@0: if (os) { michael@0: os->NotifyObservers(nullptr, "memory-pressure", MOZ_UTF16("heap-minimize")); michael@0: } michael@0: } michael@0: michael@0: static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); michael@0: michael@0: void michael@0: nsJSContext::EnsureStatics() michael@0: { michael@0: if (sIsInitialized) { michael@0: if (!nsContentUtils::XPConnect()) { michael@0: MOZ_CRASH(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, michael@0: &sSecurityManager); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: rv = CallGetService(kJSRuntimeServiceContractID, &sRuntimeService); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: rv = sRuntimeService->GetRuntime(&sRuntime); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: // Let's make sure that our main thread is the same as the xpcom main thread. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: sPrevGCSliceCallback = JS::SetGCSliceCallback(sRuntime, DOMGCSliceCallback); michael@0: michael@0: // Set up the structured clone callbacks. michael@0: static JSStructuredCloneCallbacks cloneCallbacks = { michael@0: NS_DOMReadStructuredClone, michael@0: NS_DOMWriteStructuredClone, michael@0: NS_DOMStructuredCloneError, michael@0: nullptr, michael@0: nullptr, michael@0: nullptr michael@0: }; michael@0: JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks); michael@0: michael@0: static js::DOMCallbacks DOMcallbacks = { michael@0: InstanceClassHasProtoAtDepth michael@0: }; michael@0: SetDOMCallbacks(sRuntime, &DOMcallbacks); michael@0: michael@0: // Set up the asm.js cache callbacks michael@0: static JS::AsmJSCacheOps asmJSCacheOps = { michael@0: AsmJSCacheOpenEntryForRead, michael@0: asmjscache::CloseEntryForRead, michael@0: AsmJSCacheOpenEntryForWrite, michael@0: asmjscache::CloseEntryForWrite, michael@0: asmjscache::GetBuildId michael@0: }; michael@0: JS::SetAsmJSCacheOps(sRuntime, &asmJSCacheOps); michael@0: michael@0: JS::SetLargeAllocationFailureCallback(sRuntime, OnLargeAllocationFailure); michael@0: michael@0: // Set these global xpconnect options... michael@0: Preferences::RegisterCallbackAndCall(ReportAllJSExceptionsPrefChangedCallback, michael@0: "dom.report_all_js_exceptions"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback, michael@0: "javascript.options.mem.high_water_mark"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback, michael@0: "javascript.options.mem.max"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback, michael@0: "javascript.options.mem.gc_per_compartment"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback, michael@0: "javascript.options.mem.gc_incremental"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCSliceTimePrefChangedCallback, michael@0: "javascript.options.mem.gc_incremental_slice_ms"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_high_frequency_time_limit_ms", michael@0: (void *)JSGC_HIGH_FREQUENCY_TIME_LIMIT); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicMarkSlicePrefChangedCallback, michael@0: "javascript.options.mem.gc_dynamic_mark_slice"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicHeapGrowthPrefChangedCallback, michael@0: "javascript.options.mem.gc_dynamic_heap_growth"); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_low_frequency_heap_growth", michael@0: (void *)JSGC_LOW_FREQUENCY_HEAP_GROWTH); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_high_frequency_heap_growth_min", michael@0: (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_high_frequency_heap_growth_max", michael@0: (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_high_frequency_low_limit_mb", michael@0: (void *)JSGC_HIGH_FREQUENCY_LOW_LIMIT); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_high_frequency_high_limit_mb", michael@0: (void *)JSGC_HIGH_FREQUENCY_HIGH_LIMIT); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_allocation_threshold_mb", michael@0: (void *)JSGC_ALLOCATION_THRESHOLD); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback, michael@0: "javascript.options.mem.gc_decommit_threshold_mb", michael@0: (void *)JSGC_DECOMMIT_THRESHOLD); michael@0: michael@0: Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback, michael@0: "dom.cycle_collector.incremental"); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (!obs) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: Preferences::AddBoolVarCache(&sGCOnMemoryPressure, michael@0: "javascript.options.gc_on_memory_pressure", michael@0: true); michael@0: michael@0: nsIObserver* observer = new nsJSEnvironmentObserver(); michael@0: obs->AddObserver(observer, "memory-pressure", false); michael@0: obs->AddObserver(observer, "quit-application", false); michael@0: michael@0: // Bug 907848 - We need to explicitly get the nsIDOMScriptObjectFactory michael@0: // service in order to force its constructor to run, which registers a michael@0: // shutdown observer. It would be nice to make this more explicit and less michael@0: // side-effect-y. michael@0: nsCOMPtr factory = do_GetService(kDOMScriptObjectFactoryCID); michael@0: if (!factory) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: sIsInitialized = true; michael@0: } michael@0: michael@0: nsScriptNameSpaceManager* michael@0: mozilla::dom::GetNameSpaceManager() michael@0: { michael@0: if (sDidShutdown) michael@0: return nullptr; michael@0: michael@0: if (!gNameSpaceManager) { michael@0: gNameSpaceManager = new nsScriptNameSpaceManager; michael@0: NS_ADDREF(gNameSpaceManager); michael@0: michael@0: nsresult rv = gNameSpaceManager->Init(); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: } michael@0: michael@0: return gNameSpaceManager; michael@0: } michael@0: michael@0: void michael@0: mozilla::dom::ShutdownJSEnvironment() michael@0: { michael@0: KillTimers(); michael@0: michael@0: NS_IF_RELEASE(gNameSpaceManager); michael@0: michael@0: if (!sContextCount) { michael@0: // We're being shutdown, and there are no more contexts michael@0: // alive, release the JS runtime service and the security manager. michael@0: michael@0: NS_IF_RELEASE(sRuntimeService); michael@0: NS_IF_RELEASE(sSecurityManager); michael@0: } michael@0: michael@0: sShuttingDown = true; michael@0: sDidShutdown = true; michael@0: } michael@0: michael@0: // A fast-array class for JS. This class supports both nsIJSScriptArray and michael@0: // nsIArray. If it is JS itself providing and consuming this class, all work michael@0: // can be done via nsIJSScriptArray, and avoid the conversion of elements michael@0: // to/from nsISupports. michael@0: // When consumed by non-JS (eg, another script language), conversion is done michael@0: // on-the-fly. michael@0: class nsJSArgArray MOZ_FINAL : public nsIJSArgArray { michael@0: public: michael@0: nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv, michael@0: nsresult *prv); michael@0: ~nsJSArgArray(); michael@0: // nsISupports michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray, michael@0: nsIJSArgArray) michael@0: michael@0: // nsIArray michael@0: NS_DECL_NSIARRAY michael@0: michael@0: // nsIJSArgArray michael@0: nsresult GetArgs(uint32_t *argc, void **argv); michael@0: michael@0: void ReleaseJSObjects(); michael@0: michael@0: protected: michael@0: JSContext *mContext; michael@0: JS::Heap *mArgv; michael@0: uint32_t mArgc; michael@0: }; michael@0: michael@0: nsJSArgArray::nsJSArgArray(JSContext *aContext, uint32_t argc, JS::Value *argv, michael@0: nsresult *prv) : michael@0: mContext(aContext), michael@0: mArgv(nullptr), michael@0: mArgc(argc) michael@0: { michael@0: // copy the array - we don't know its lifetime, and ours is tied to xpcom michael@0: // refcounting. michael@0: if (argc) { michael@0: static const fallible_t fallible = fallible_t(); michael@0: mArgv = new (fallible) JS::Heap[argc]; michael@0: if (!mArgv) { michael@0: *prv = NS_ERROR_OUT_OF_MEMORY; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Callers are allowed to pass in a null argv even for argc > 0. They can michael@0: // then use GetArgs to initialize the values. michael@0: if (argv) { michael@0: for (uint32_t i = 0; i < argc; ++i) michael@0: mArgv[i] = argv[i]; michael@0: } michael@0: michael@0: if (argc > 0) { michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: *prv = NS_OK; michael@0: } michael@0: michael@0: nsJSArgArray::~nsJSArgArray() michael@0: { michael@0: ReleaseJSObjects(); michael@0: } michael@0: michael@0: void michael@0: nsJSArgArray::ReleaseJSObjects() michael@0: { michael@0: if (mArgv) { michael@0: delete [] mArgv; michael@0: } michael@0: if (mArgc > 0) { michael@0: mArgc = 0; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: } michael@0: michael@0: // QueryInterface implementation for nsJSArgArray michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray) michael@0: tmp->ReleaseJSObjects(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray) michael@0: if (tmp->mArgv) { michael@0: for (uint32_t i = 0; i < tmp->mArgc; ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mArgv[i]) michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray) michael@0: NS_INTERFACE_MAP_ENTRY(nsIArray) michael@0: NS_INTERFACE_MAP_ENTRY(nsIJSArgArray) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray) michael@0: michael@0: nsresult michael@0: nsJSArgArray::GetArgs(uint32_t *argc, void **argv) michael@0: { michael@0: *argv = (void *)mArgv; michael@0: *argc = mArgc; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIArray impl michael@0: NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t *aLength) michael@0: { michael@0: *aLength = mArgc; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void queryElementAt (in unsigned long index, in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ michael@0: NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID & uuid, void * *result) michael@0: { michael@0: *result = nullptr; michael@0: if (index >= mArgc) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (uuid.Equals(NS_GET_IID(nsIVariant)) || uuid.Equals(NS_GET_IID(nsISupports))) { michael@0: // Have to copy a Heap into a Rooted to work with it. michael@0: JS::Rooted val(mContext, mArgv[index]); michael@0: return nsContentUtils::XPConnect()->JSToVariant(mContext, val, michael@0: (nsIVariant **)result); michael@0: } michael@0: NS_WARNING("nsJSArgArray only handles nsIVariant"); michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: /* unsigned long indexOf (in unsigned long startIndex, in nsISupports element); */ michael@0: NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports *element, uint32_t *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* nsISimpleEnumerator enumerate (); */ michael@0: NS_IMETHODIMP nsJSArgArray::Enumerate(nsISimpleEnumerator **_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // The factory function michael@0: nsresult NS_CreateJSArgv(JSContext *aContext, uint32_t argc, void *argv, michael@0: nsIJSArgArray **aArray) michael@0: { michael@0: nsresult rv; michael@0: nsJSArgArray *ret = new nsJSArgArray(aContext, argc, michael@0: static_cast(argv), &rv); michael@0: if (ret == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: if (NS_FAILED(rv)) { michael@0: delete ret; michael@0: return rv; michael@0: } michael@0: return ret->QueryInterface(NS_GET_IID(nsIArray), (void **)aArray); michael@0: }