/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsError.h" #include "nsJSEnvironment.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptObjectPrincipal.h" #include "nsPIDOMWindow.h" #include "nsDOMCID.h" #include "nsIXPConnect.h" #include "nsCOMPtr.h" #include "nsISupportsPrimitives.h" #include "nsReadableUtils.h" #include "nsDOMJSUtils.h" #include "nsJSUtils.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsPresContext.h" #include "nsIConsoleService.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIObserverService.h" #include "nsITimer.h" #include "nsAtom.h" #include "nsContentUtils.h" #include "mozilla/EventDispatcher.h" #include "mozilla/HoldDropJSObjects.h" #include "nsIContent.h" #include "nsCycleCollector.h" #include "nsXPCOMCIDInternal.h" #include "nsServiceManagerUtils.h" #include "nsTextFormatter.h" #ifdef XP_WIN # include # define getpid _getpid #else # include // for getpid() #endif #include "xpcpublic.h" #include "jsapi.h" #include "js/Array.h" // JS::NewArrayObject #include "js/PropertyAndElement.h" // JS_DefineProperty #include "js/PropertySpec.h" #include "js/SliceBudget.h" #include "js/Wrapper.h" #include "nsIArray.h" #include "CCGCScheduler.h" #include "WrapperFactory.h" #include "nsGlobalWindow.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/PresShell.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/StaticPrefs_javascript.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/DOMExceptionBinding.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/FetchUtil.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SerializedStackHolder.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "nsRefreshDriver.h" #include "nsJSPrincipals.h" #include "AccessCheck.h" #include "mozilla/Logging.h" #include "prthread.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/Attributes.h" #include "mozilla/dom/CanvasRenderingContext2DBinding.h" #include "mozilla/ContentEvents.h" #include "mozilla/CycleCollectedJSContext.h" #include "nsCycleCollectionNoteRootCallback.h" #include "nsViewManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" using namespace mozilla; using namespace mozilla::dom; // Thank you Microsoft! #ifdef CompareString # undef CompareString #endif static JS::GCSliceCallback sPrevGCSliceCallback; static bool sIncrementalCC = false; static bool sIsInitialized; static bool sShuttingDown; static CCGCScheduler sScheduler; struct CycleCollectorStats { constexpr CycleCollectorStats() = default; void Init(); void Clear(); void PrepareForCycleCollection(TimeStamp aNow); void AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime); void AfterCycleCollectionSlice(); void AfterSyncForgetSkippable(TimeStamp beginTime); void AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples); void AfterCycleCollection(); void SendTelemetry(TimeDuration aCCNowDuration, TimeStamp aPrevCCEnd) const; void MaybeLogStats(const CycleCollectorResults& aResults, uint32_t aCleanups) const; void MaybeNotifyStats(const CycleCollectorResults& aResults, TimeDuration aCCNowDuration, uint32_t aCleanups) const; // Time the current slice began, including any GC finishing. TimeStamp mBeginSliceTime; // Time the previous slice of the current CC ended. TimeStamp mEndSliceTime; // Time the current cycle collection began. TimeStamp mBeginTime; // The longest GC finishing duration for any slice of the current CC. TimeDuration mMaxGCDuration; // True if we ran sync forget skippable in any slice of the current CC. bool mRanSyncForgetSkippable = false; // Number of suspected objects at the start of the current CC. uint32_t mSuspected = 0; // The longest duration spent on sync forget skippable in any slice of the // current CC. TimeDuration mMaxSkippableDuration; // The longest pause of any slice in the current CC. TimeDuration mMaxSliceTime; // The longest slice time since ClearMaxCCSliceTime() was called. TimeDuration mMaxSliceTimeSinceClear; // The total amount of time spent actually running the current CC. TimeDuration mTotalSliceTime; // True if we were locked out by the GC in any slice of the current CC. bool mAnyLockedOut = false; // A file to dump CC activity to; set by MOZ_CCTIMER environment variable. FILE* mFile = nullptr; // In case CC slice was triggered during idle time, set to the end of the idle // period. TimeStamp mIdleDeadline; TimeDuration mMinForgetSkippableTime; TimeDuration mMaxForgetSkippableTime; TimeDuration mTotalForgetSkippableTime; uint32_t mForgetSkippableBeforeCC = 0; uint32_t mRemovedPurples = 0; }; static CycleCollectorStats sCCStats; static const char* ProcessNameForCollectorLog() { return XRE_GetProcessType() == GeckoProcessType_Default ? "default" : "content"; } namespace xpc { // This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC // Exceptions, and arbitrary values that were associated with a stack by the // JS engine when they were thrown, as specified by exceptionStack. // // Note that the returned stackObj and stackGlobal are _not_ wrapped into the // compartment of exceptionValue. void FindExceptionStackForConsoleReport( nsPIDOMWindowInner* win, JS::Handle exceptionValue, JS::Handle exceptionStack, JS::MutableHandle stackObj, JS::MutableHandle stackGlobal) { stackObj.set(nullptr); stackGlobal.set(nullptr); if (!exceptionValue.isObject()) { // Use the stack provided by the JS engine, if available. This will not be // a wrapper. if (exceptionStack) { stackObj.set(exceptionStack); stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack)); } return; } if (win && win->AsGlobal()->IsDying()) { // Pretend like we have no stack, so we don't end up keeping the global // alive via the stack. return; } JS::RootingContext* rcx = RootingCx(); JS::Rooted exceptionObject(rcx, &exceptionValue.toObject()); if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) { // At this point we know exceptionObject is a possibly-wrapped // js::ErrorObject that has excStack as stack. excStack might also be a CCW, // but excStack must be same-compartment with the unwrapped ErrorObject. // Return the ErrorObject's global as stackGlobal. This matches what we do // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal // are same-compartment. JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject); stackObj.set(excStack); stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException)); return; } // It is not a JS Exception, try DOM Exception. RefPtr exception; UNWRAP_OBJECT(DOMException, exceptionObject, exception); if (!exception) { // Not a DOM Exception, try XPC Exception. UNWRAP_OBJECT(Exception, exceptionObject, exception); if (!exception) { // As above, use the stack provided by the JS engine, if available. if (exceptionStack) { stackObj.set(exceptionStack); stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack)); } return; } } nsCOMPtr stack = exception->GetLocation(); if (!stack) { return; } JS::Rooted value(rcx); stack->GetNativeSavedFrame(&value); if (value.isObject()) { stackObj.set(&value.toObject()); MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj)); stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj)); return; } } } /* namespace xpc */ static TimeDuration GetCollectionTimeDelta() { static TimeStamp sFirstCollectionTime; TimeStamp now = TimeStamp::Now(); if (sFirstCollectionTime) { return now - sFirstCollectionTime; } sFirstCollectionTime = now; return TimeDuration(); } class nsJSEnvironmentObserver final : public nsIObserver { ~nsJSEnvironmentObserver() = default; public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER }; NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver) NS_IMETHODIMP nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!nsCRT::strcmp(aTopic, "memory-pressure")) { if (StaticPrefs::javascript_options_gc_on_memory_pressure()) { if (sShuttingDown) { // Don't GC/CC if we're already shutting down. return NS_OK; } nsDependentString data(aData); if (data.EqualsLiteral("low-memory-ongoing")) { // Don't GC/CC if we are in an ongoing low-memory state since its very // slow and it likely won't help us anyway. return NS_OK; } if (data.EqualsLiteral("heap-minimize")) { // heap-minimize notifiers expect this to run synchronously nsJSContext::DoLowMemoryGC(); return NS_OK; } if (data.EqualsLiteral("low-memory")) { nsJSContext::SetLowMemoryState(true); } // Asynchronously GC. nsJSContext::LowMemoryGC(); } } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) { nsJSContext::SetLowMemoryState(false); } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) { sScheduler.UserIsInactive(); } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) { sScheduler.UserIsActive(); } else if (!nsCRT::strcmp(aTopic, "quit-application") || !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) || !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) { sShuttingDown = true; sScheduler.Shutdown(); } return NS_OK; } /**************************************************************** ************************** AutoFree **************************** ****************************************************************/ class AutoFree { public: explicit AutoFree(void* aPtr) : mPtr(aPtr) {} ~AutoFree() { if (mPtr) free(mPtr); } void Invalidate() { mPtr = nullptr; } private: void* mPtr; }; // A utility function for script languages to call. Although it looks small, // the use of nsIDocShell and nsPresContext triggers a huge number of // dependencies that most languages would not otherwise need. // XXXmarkh - This function is mis-placed! bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal, const ErrorEventInit& aErrorEventInit, nsEventStatus* aStatus) { bool called = false; nsCOMPtr win(do_QueryInterface(aScriptGlobal)); nsIDocShell* docShell = win ? win->GetDocShell() : nullptr; if (docShell) { RefPtr presContext = docShell->GetPresContext(); static int32_t errorDepth; // Recursion prevention ++errorDepth; if (errorDepth < 2) { // Dispatch() must be synchronous for the recursion block // (errorDepth) to work. RefPtr event = ErrorEvent::Constructor( nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit); event->SetTrusted(true); EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext, aStatus); called = true; } --errorDepth; } return called; } class ScriptErrorEvent : public Runnable { public: ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx, xpc::ErrorReport* aReport, JS::Handle aError, JS::Handle aErrorStack) : mozilla::Runnable("ScriptErrorEvent"), mWindow(aWindow), mReport(aReport), mError(aRootingCx, aError), mErrorStack(aRootingCx, aErrorStack) {} // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { nsEventStatus status = nsEventStatus_eIgnore; nsCOMPtr win = mWindow; MOZ_ASSERT(win); MOZ_ASSERT(NS_IsMainThread()); // First, notify the DOM that we have a script error, but only if // our window is still the current inner. JS::RootingContext* rootingCx = RootingCx(); if (win->IsCurrentInnerWindow() && win->GetDocShell() && !sHandlingScriptError) { AutoRestore recursionGuard(sHandlingScriptError); sHandlingScriptError = true; RefPtr presContext = win->GetDocShell()->GetPresContext(); RootedDictionary init(rootingCx); init.mCancelable = true; init.mFilename = mReport->mFileName; init.mBubbles = true; constexpr auto xoriginMsg = u"Script error."_ns; if (!mReport->mIsMuted) { init.mMessage = mReport->mErrorMsg; init.mLineno = mReport->mLineNumber; init.mColno = mReport->mColumn; init.mError = mError; } else { NS_WARNING("Not same origin error!"); init.mMessage = xoriginMsg; init.mLineno = 0; } RefPtr event = ErrorEvent::Constructor( nsGlobalWindowInner::Cast(win), u"error"_ns, init); event->SetTrusted(true); EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext, &status); } if (status != nsEventStatus_eConsumeNoDefault) { JS::Rooted stack(rootingCx); JS::Rooted stackGlobal(rootingCx); xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack, &stackGlobal); JS::Rooted> exception(rootingCx, Some(mError)); nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win); mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal); } return NS_OK; } private: nsCOMPtr mWindow; RefPtr mReport; JS::PersistentRooted mError; JS::PersistentRooted mErrorStack; static bool sHandlingScriptError; }; bool ScriptErrorEvent::sHandlingScriptError = false; // This temporarily lives here to avoid code churn. It will go away entirely // soon. namespace xpc { void DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, xpc::ErrorReport* xpcReport, JS::Handle exception, JS::Handle exceptionStack) { nsContentUtils::AddScriptRunner(new ScriptErrorEvent( win, rootingCx, xpcReport, exception, exceptionStack)); } } /* namespace xpc */ #ifdef DEBUG // A couple of useful functions to call when you're debugging. nsGlobalWindowInner* JSObject2Win(JSObject* obj) { return xpc::WindowOrNull(obj); } template void PrintWinURI(T* win) { if (!win) { printf("No window passed in.\n"); return; } nsCOMPtr doc = win->GetExtantDoc(); if (!doc) { printf("No document in the window.\n"); return; } nsIURI* uri = doc->GetDocumentURI(); if (!uri) { printf("Document doesn't have a URI.\n"); return; } printf("%s\n", uri->GetSpecOrDefault().get()); } void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); } void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); } template void PrintWinCodebase(T* win) { if (!win) { printf("No window passed in.\n"); return; } nsIPrincipal* prin = win->GetPrincipal(); if (!prin) { printf("Window doesn't have principals.\n"); return; } if (prin->IsSystemPrincipal()) { printf("No URI, it's the system principal.\n"); return; } nsCString spec; prin->GetAsciiSpec(spec); printf("%s\n", spec.get()); } void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) { return PrintWinCodebase(aWin); } void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) { return PrintWinCodebase(aWin); } void DumpString(const nsAString& str) { printf("%s\n", NS_ConvertUTF16toUTF8(str).get()); } #endif nsJSContext::nsJSContext(bool aGCOnDestruction, nsIScriptGlobalObject* aGlobalObject) : mWindowProxy(nullptr), mGCOnDestruction(aGCOnDestruction), mGlobalObjectRef(aGlobalObject) { EnsureStatics(); mProcessingScriptTag = false; HoldJSObjects(this); } nsJSContext::~nsJSContext() { mGlobalObjectRef = nullptr; Destroy(); } void nsJSContext::Destroy() { if (mGCOnDestruction) { sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy); } DropJSObjects(this); } // QueryInterface implementation for nsJSContext NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext) tmp->mGCOnDestruction = false; tmp->mWindowProxy = nullptr; tmp->Destroy(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext) NS_INTERFACE_MAP_ENTRY(nsIScriptContext) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext) #ifdef DEBUG bool AtomIsEventHandlerName(nsAtom* aName) { const char16_t* name = aName->GetUTF16String(); const char16_t* cp; char16_t c; for (cp = name; *cp != '\0'; ++cp) { c = *cp; if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false; } return true; } #endif nsIScriptGlobalObject* nsJSContext::GetGlobalObject() { // Note: this could probably be simplified somewhat more; see bug 974327 // comments 1 and 3. if (!mWindowProxy) { return nullptr; } MOZ_ASSERT(mGlobalObjectRef); return mGlobalObjectRef; } nsresult nsJSContext::SetProperty(JS::Handle aTarget, const char* aPropName, nsISupports* aArgs) { AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) { return NS_ERROR_FAILURE; } JSContext* cx = jsapi.cx(); JS::RootedVector args(cx); JS::Rooted global(cx, GetWindowProxy()); nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args); NS_ENSURE_SUCCESS(rv, rv); // got the arguments, now attach them. for (uint32_t i = 0; i < args.length(); ++i) { if (!JS_WrapValue(cx, args[i])) { return NS_ERROR_FAILURE; } } JS::Rooted array(cx, JS::NewArrayObject(cx, args)); if (!array) { return NS_ERROR_FAILURE; } return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK : NS_ERROR_FAILURE; } nsresult nsJSContext::ConvertSupportsTojsvals( JSContext* aCx, nsISupports* aArgs, JS::Handle aScope, JS::MutableHandleVector aArgsOut) { nsresult rv = NS_OK; // If the array implements nsIJSArgArray, copy the contents and return. nsCOMPtr fastArray = do_QueryInterface(aArgs); if (fastArray) { uint32_t argc; JS::Value* argv; rv = fastArray->GetArgs(&argc, reinterpret_cast(&argv)); if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) { rv = NS_ERROR_OUT_OF_MEMORY; } return rv; } // Take the slower path converting each item. // Handle only nsIArray and nsIVariant. nsIArray is only needed for // SetProperty('arguments', ...); nsIXPConnect* xpc = nsContentUtils::XPConnect(); NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); if (!aArgs) return NS_OK; uint32_t argCount; // This general purpose function may need to convert an arg array // (window.arguments, event-handler args) and a generic property. nsCOMPtr argsArray(do_QueryInterface(aArgs)); if (argsArray) { rv = argsArray->GetLength(&argCount); NS_ENSURE_SUCCESS(rv, rv); if (argCount == 0) return NS_OK; } else { argCount = 1; // the nsISupports which is not an array } // Use the caller's auto guards to release and unroot. if (!aArgsOut.resize(argCount)) { return NS_ERROR_OUT_OF_MEMORY; } if (argsArray) { for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) { nsCOMPtr arg; JS::MutableHandle thisVal = aArgsOut[argCtr]; argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports), getter_AddRefs(arg)); if (!arg) { thisVal.setNull(); continue; } nsCOMPtr variant(do_QueryInterface(arg)); if (variant != nullptr) { rv = xpc->VariantToJS(aCx, aScope, variant, thisVal); } else { // And finally, support the nsISupportsPrimitives supplied // by the AppShell. It generally will pass only strings, but // as we have code for handling all, we may as well use it. rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address()); if (rv == NS_ERROR_NO_INTERFACE) { // something else - probably an event object or similar - // just wrap it. #ifdef DEBUG // but first, check its not another nsISupportsPrimitive, as // these are now deprecated for use with script contexts. nsCOMPtr prim(do_QueryInterface(arg)); NS_ASSERTION(prim == nullptr, "Don't pass nsISupportsPrimitives - use nsIVariant!"); #endif JSAutoRealm ar(aCx, aScope); rv = nsContentUtils::WrapNative(aCx, arg, thisVal); } } } } else { nsCOMPtr variant = do_QueryInterface(aArgs); if (variant) { rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]); } else { NS_ERROR("Not an array, not an interface?"); rv = NS_ERROR_UNEXPECTED; } } return rv; } // This really should go into xpconnect somewhere... nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx, nsISupports* aArg, JS::Value* aArgv) { MOZ_ASSERT(aArg, "Empty arg"); nsCOMPtr argPrimitive(do_QueryInterface(aArg)); if (!argPrimitive) return NS_ERROR_NO_INTERFACE; uint16_t type; argPrimitive->GetType(&type); switch (type) { case nsISupportsPrimitive::TYPE_CSTRING: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); nsAutoCString data; p->GetData(data); JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length()); NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); aArgv->setString(str); break; } case nsISupportsPrimitive::TYPE_STRING: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); nsAutoString data; p->GetData(data); // cast is probably safe since wchar_t and char16_t are expected // to be equivalent; both unsigned 16-bit entities JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length()); NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); aArgv->setString(str); break; } case nsISupportsPrimitive::TYPE_PRBOOL: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); bool data; p->GetData(&data); aArgv->setBoolean(data); break; } case nsISupportsPrimitive::TYPE_PRUINT8: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); uint8_t data; p->GetData(&data); aArgv->setInt32(data); break; } case nsISupportsPrimitive::TYPE_PRUINT16: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); uint16_t data; p->GetData(&data); aArgv->setInt32(data); break; } case nsISupportsPrimitive::TYPE_PRUINT32: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); uint32_t data; p->GetData(&data); aArgv->setInt32(data); break; } case nsISupportsPrimitive::TYPE_CHAR: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); char data; p->GetData(&data); JSString* str = ::JS_NewStringCopyN(aCx, &data, 1); NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY); aArgv->setString(str); break; } case nsISupportsPrimitive::TYPE_PRINT16: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); int16_t data; p->GetData(&data); aArgv->setInt32(data); break; } case nsISupportsPrimitive::TYPE_PRINT32: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); int32_t data; p->GetData(&data); aArgv->setInt32(data); break; } case nsISupportsPrimitive::TYPE_FLOAT: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); float data; p->GetData(&data); *aArgv = ::JS_NumberValue(data); break; } case nsISupportsPrimitive::TYPE_DOUBLE: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); double data; p->GetData(&data); *aArgv = ::JS_NumberValue(data); break; } case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: { nsCOMPtr p(do_QueryInterface(argPrimitive)); NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED); nsCOMPtr data; nsIID* iid = nullptr; p->GetData(getter_AddRefs(data)); p->GetDataIID(&iid); NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED); AutoFree iidGuard(iid); // Free iid upon destruction. JS::Rooted scope(aCx, GetWindowProxy()); JS::Rooted v(aCx); JSAutoRealm ar(aCx, scope); nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v); NS_ENSURE_SUCCESS(rv, rv); *aArgv = v; break; } case nsISupportsPrimitive::TYPE_ID: case nsISupportsPrimitive::TYPE_PRUINT64: case nsISupportsPrimitive::TYPE_PRINT64: case nsISupportsPrimitive::TYPE_PRTIME: { NS_WARNING("Unsupported primitive type used"); aArgv->setNull(); break; } default: { NS_WARNING("Unknown primitive type used"); aArgv->setNull(); break; } } return NS_OK; } #ifdef MOZ_JPROF # include inline bool IsJProfAction(struct sigaction* action) { return (action->sa_sigaction && (action->sa_flags & (SA_RESTART | SA_SIGINFO)) == (SA_RESTART | SA_SIGINFO)); } void NS_JProfStartProfiling(); void NS_JProfStopProfiling(); void NS_JProfClearCircular(); static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) { NS_JProfStartProfiling(); return true; } void NS_JProfStartProfiling() { // Figure out whether we're dealing with SIGPROF, SIGALRM, or // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for // JP_RTC_HZ) struct sigaction action; // Must check ALRM before PROF since both are enabled for real-time sigaction(SIGALRM, nullptr, &action); // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); if (IsJProfAction(&action)) { // printf("Beginning real-time jprof profiling.\n"); raise(SIGALRM); return; } sigaction(SIGPROF, nullptr, &action); // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); if (IsJProfAction(&action)) { // printf("Beginning process-time jprof profiling.\n"); raise(SIGPROF); return; } sigaction(SIGPOLL, nullptr, &action); // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags); if (IsJProfAction(&action)) { // printf("Beginning rtc-based jprof profiling.\n"); raise(SIGPOLL); return; } printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n"); } static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) { NS_JProfStopProfiling(); return true; } void NS_JProfStopProfiling() { raise(SIGUSR1); // printf("Stopped jprof profiling.\n"); } static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) { NS_JProfClearCircular(); return true; } void NS_JProfClearCircular() { raise(SIGUSR2); // printf("cleared jprof buffer\n"); } static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) { // Not ideal... NS_JProfStopProfiling(); NS_JProfStartProfiling(); return true; } static const JSFunctionSpec JProfFunctions[] = { JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0), JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0), JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0), JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END}; #endif /* defined(MOZ_JPROF) */ nsresult nsJSContext::InitClasses(JS::Handle aGlobalObj) { AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); JSAutoRealm ar(cx, aGlobalObj); #ifdef MOZ_JPROF // Attempt to initialize JProf functions ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions); #endif return NS_OK; } bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; } void nsJSContext::SetProcessingScriptTag(bool aFlag) { mProcessingScriptTag = aFlag; } // static void nsJSContext::SetLowMemoryState(bool aState) { JSContext* cx = danger::GetJSContext(); JS::SetLowMemoryState(cx, aState); } static void GarbageCollectImpl(JS::GCReason aReason, nsJSContext::IsShrinking aShrinking, const js::SliceBudget& aBudget) { AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason)); bool wantIncremental = !aBudget.isUnlimited(); // We use danger::GetJSContext() since AutoJSAPI will assert if the current // thread's context is null (such as during shutdown). JSContext* cx = danger::GetJSContext(); if (!nsContentUtils::XPConnect() || !cx) { return; } if (sScheduler.InIncrementalGC() && wantIncremental) { // We're in the middle of incremental GC. Do another slice. JS::PrepareForIncrementalGC(cx); JS::IncrementalGCSlice(cx, aReason, aBudget); return; } JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC ? JS::GCOptions::Shrink : JS::GCOptions::Normal; if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) { sScheduler.SetNeedsFullGC(); } if (sScheduler.NeedsFullGC()) { JS::PrepareForFullGC(cx); } if (wantIncremental) { // Incremental GC slices will be triggered by the GC Runner. If one doesn't // already exist, create it in the GC_SLICE_END callback for the first // slice being executed here. JS::StartIncrementalGC(cx, options, aReason, aBudget); } else { JS::NonIncrementalGC(cx, options, aReason); } } // static void nsJSContext::GarbageCollectNow(JS::GCReason aReason, IsShrinking aShrinking) { GarbageCollectImpl(aReason, aShrinking, js::SliceBudget::unlimited()); } // static void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason, IsShrinking aShrinking, js::SliceBudget& aBudget) { AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC); GarbageCollectImpl(aReason, aShrinking, aBudget); } static void FinishAnyIncrementalGC() { AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC); if (sScheduler.InIncrementalGC()) { AutoJSAPI jsapi; jsapi.Init(); // We're in the middle of an incremental GC, so finish it. JS::PrepareForIncrementalGC(jsapi.cx()); JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED); } } namespace geckoprofiler::markers { class CCSliceMarker { public: static constexpr Span MarkerTypeName() { return mozilla::MakeStringSpan("CCSlice"); } static void StreamJSONMarkerData( mozilla::baseprofiler::SpliceableJSONWriter& aWriter, bool aIsDuringIdle) { aWriter.BoolProperty("idle", aIsDuringIdle); } static MarkerSchema MarkerTypeDisplay() { using MS = MarkerSchema; MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, MS::Location::TimelineMemory}; schema.SetAllLabels("{marker.name} (idle={marker.data.idle})"); schema.AddKeyLabelFormat("idle", "Idle", MS::Format::Integer); return schema; } }; } // namespace geckoprofiler::markers static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) { TimeStamp startTimeStamp = TimeStamp::Now(); FinishAnyIncrementalGC(); uint32_t suspectedBefore = nsCycleCollector_suspectedCount(); js::SliceBudget budget = sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline); bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable(); nsCycleCollector_forgetSkippable(budget, aRemoveChildless, earlyForgetSkippable); TimeStamp now = TimeStamp::Now(); uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete( now, suspectedBefore, nsCycleCollector_suspectedCount()); TimeDuration duration = now - startTimeStamp; sCCStats.AfterForgetSkippable(duration, removedPurples); if (duration.ToSeconds()) { TimeDuration idleDuration; if (!aDeadline.IsNull()) { if (aDeadline < now) { // This slice overflowed the idle period. if (aDeadline > startTimeStamp) { idleDuration = aDeadline - startTimeStamp; } } else { idleDuration = duration; } } uint32_t percent = uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100); Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent); } PROFILER_MARKER("ForgetSkippable", GCCC, MarkerTiming::IntervalUntilNowFrom(startTimeStamp), CCSliceMarker, !aDeadline.IsNull()); } MOZ_ALWAYS_INLINE static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) { MOZ_ASSERT(aEnd >= aStart); return aEnd - aStart; } static TimeDuration TimeUntilNow(TimeStamp start) { if (start.IsNull()) { return TimeDuration(); } return TimeBetween(start, TimeStamp::Now()); } void CycleCollectorStats::Init() { Clear(); char* env = getenv("MOZ_CCTIMER"); if (!env) { return; } if (strcmp(env, "none") == 0) { mFile = nullptr; } else if (strcmp(env, "stdout") == 0) { mFile = stdout; } else if (strcmp(env, "stderr") == 0) { mFile = stderr; } else { mFile = fopen(env, "a"); if (!mFile) { MOZ_CRASH("Failed to open MOZ_CCTIMER log file."); } } } void CycleCollectorStats::Clear() { if (mFile && mFile != stdout && mFile != stderr) { fclose(mFile); } *this = CycleCollectorStats(); } void CycleCollectorStats::AfterCycleCollectionSlice() { if (mBeginSliceTime.IsNull()) { // We already called this method from EndCycleCollectionCallback for this // slice. return; } mEndSliceTime = TimeStamp::Now(); TimeDuration duration = mEndSliceTime - mBeginSliceTime; PROFILER_MARKER( "CCSlice", GCCC, MarkerTiming::Interval(mBeginSliceTime, mEndSliceTime), CCSliceMarker, !mIdleDeadline.IsNull() && mIdleDeadline >= mEndSliceTime); if (duration.ToSeconds()) { TimeDuration idleDuration; if (!mIdleDeadline.IsNull()) { if (mIdleDeadline < mEndSliceTime) { // This slice overflowed the idle period. if (mIdleDeadline > mBeginSliceTime) { idleDuration = mIdleDeadline - mBeginSliceTime; } } else { idleDuration = duration; } } uint32_t percent = uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE, percent); } TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime); mMaxSliceTime = std::max(mMaxSliceTime, sliceTime); mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime); mTotalSliceTime += sliceTime; mBeginSliceTime = TimeStamp(); } void CycleCollectorStats::PrepareForCycleCollection(TimeStamp aNow) { mBeginTime = aNow; mSuspected = nsCycleCollector_suspectedCount(); } void CycleCollectorStats::AfterPrepareForCycleCollectionSlice( TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime) { mBeginSliceTime = aBeginTime; mIdleDeadline = aDeadline; if (!aMaybeAfterGCTime.IsNull()) { mAnyLockedOut = true; mMaxGCDuration = std::max(mMaxGCDuration, aMaybeAfterGCTime - aBeginTime); } } void CycleCollectorStats::AfterSyncForgetSkippable(TimeStamp beginTime) { mMaxSkippableDuration = std::max(mMaxSkippableDuration, TimeUntilNow(beginTime)); mRanSyncForgetSkippable = true; } void CycleCollectorStats::AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples) { if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) { mMinForgetSkippableTime = duration; } if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) { mMaxForgetSkippableTime = duration; } mTotalForgetSkippableTime += duration; ++mForgetSkippableBeforeCC; mRemovedPurples += aRemovedPurples; } void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration, TimeStamp aPrevCCEnd) const { Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, mRanSyncForgetSkippable); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, aCCNowDuration.ToMilliseconds()); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, mMaxSliceTime.ToMilliseconds()); if (!aPrevCCEnd.IsNull()) { TimeDuration timeBetween = TimeBetween(aPrevCCEnd, mBeginTime); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween.ToSeconds()); } Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX, mMaxForgetSkippableTime.ToMilliseconds()); } void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults, uint32_t aCleanups) const { if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) { return; } TimeDuration delta = GetCollectionTimeDelta(); nsCString mergeMsg; if (aResults.mMergedZones) { mergeMsg.AssignLiteral(" merged"); } nsCString gcMsg; if (aResults.mForcedGC) { gcMsg.AssignLiteral(", forced a GC"); } const char16_t* kFmt = u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, " u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu " u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n" u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: " u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu"; nsString msg; nsTextFormatter::ssprintf( msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC, ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(), aResults.mFreedRefCounted, aResults.mFreedGCed, sScheduler.mCCollectedWaitingForGC, sScheduler.mCCollectedZonesWaitingForGC, sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(), mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(), mMaxForgetSkippableTime.ToMilliseconds(), mTotalForgetSkippableTime.ToMilliseconds() / aCleanups, mTotalForgetSkippableTime.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples); if (StaticPrefs::javascript_options_mem_log()) { nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { cs->LogStringMessage(msg.get()); } } if (mFile) { fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get()); } } void CycleCollectorStats::MaybeNotifyStats( const CycleCollectorResults& aResults, TimeDuration aCCNowDuration, uint32_t aCleanups) const { if (!StaticPrefs::javascript_options_mem_notify()) { return; } const char16_t* kJSONFmt = u"{ \"timestamp\": %llu, " u"\"duration\": %.f, " u"\"max_slice_pause\": %.f, " u"\"total_slice_pause\": %.f, " u"\"max_finish_gc_duration\": %.f, " u"\"max_sync_skippable_duration\": %.f, " u"\"suspected\": %lu, " u"\"visited\": { " u"\"RCed\": %lu, " u"\"GCed\": %lu }, " u"\"collected\": { " u"\"RCed\": %lu, " u"\"GCed\": %lu }, " u"\"waiting_for_gc\": %lu, " u"\"zones_waiting_for_gc\": %lu, " u"\"short_living_objects_waiting_for_gc\": %lu, " u"\"forced_gc\": %d, " u"\"forget_skippable\": { " u"\"times_before_cc\": %lu, " u"\"min\": %.f, " u"\"max\": %.f, " u"\"avg\": %.f, " u"\"total\": %.f, " u"\"removed\": %lu } " u"}"; nsString json; nsTextFormatter::ssprintf( json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(), mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(), mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(), mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed, aResults.mFreedRefCounted, aResults.mFreedGCed, sScheduler.mCCollectedWaitingForGC, sScheduler.mCCollectedZonesWaitingForGC, sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC, mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(), mMaxForgetSkippableTime.ToMilliseconds(), mTotalForgetSkippableTime.ToMilliseconds() / aCleanups, mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->NotifyObservers(nullptr, "cycle-collection-statistics", json.get()); } } // static void nsJSContext::CycleCollectNow(CCReason aReason, nsICycleCollectorListener* aListener) { if (!NS_IsMainThread()) { return; } AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC); PrepareForCycleCollectionSlice(aReason, TimeStamp()); nsCycleCollector_collect(aReason, aListener); sCCStats.AfterCycleCollectionSlice(); } // static void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason, TimeStamp aDeadline) { TimeStamp beginTime = TimeStamp::Now(); // Before we begin the cycle collection, make sure there is no active GC. TimeStamp afterGCTime; if (sScheduler.InIncrementalGC()) { FinishAnyIncrementalGC(); afterGCTime = TimeStamp::Now(); } if (!sScheduler.IsCollectingCycles()) { sCCStats.PrepareForCycleCollection(beginTime); sScheduler.NoteCCBegin(aReason, beginTime, sCCStats.mForgetSkippableBeforeCC, sCCStats.mSuspected, sCCStats.mRemovedPurples); } sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime, afterGCTime); } // static void nsJSContext::RunCycleCollectorSlice(CCReason aReason, TimeStamp aDeadline) { if (!NS_IsMainThread()) { return; } PrepareForCycleCollectionSlice(aReason, aDeadline); // Decide how long we want to budget for this slice. if (sIncrementalCC) { bool preferShorterSlices; js::SliceBudget budget = sScheduler.ComputeCCSliceBudget( aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime, TimeStamp::Now(), &preferShorterSlices); nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices); } else { js::SliceBudget budget = js::SliceBudget::unlimited(); nsCycleCollector_collectSlice(budget, aReason, false); } sCCStats.AfterCycleCollectionSlice(); } // static void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) { if (!NS_IsMainThread()) { return; } AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC); PrepareForCycleCollectionSlice(CCReason::API, TimeStamp()); js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget)); nsCycleCollector_collectSlice(budget, CCReason::API); sCCStats.AfterCycleCollectionSlice(); } void nsJSContext::ClearMaxCCSliceTime() { sCCStats.mMaxSliceTimeSinceClear = TimeDuration(); } uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() { return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds(); } // static void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) { MOZ_ASSERT(NS_IsMainThread()); TimeStamp startTime = TimeStamp::Now(); sCCStats.PrepareForCycleCollection(startTime); // Run forgetSkippable synchronously to reduce the size of the CC graph. This // is particularly useful if we recently finished a GC. if (sScheduler.IsEarlyForgetSkippable()) { while (sScheduler.IsEarlyForgetSkippable()) { FireForgetSkippable(false, TimeStamp()); } sCCStats.AfterSyncForgetSkippable(startTime); } if (sShuttingDown) { return; } sScheduler.InitCCRunnerStateMachine( mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason); sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget); } // static void nsJSContext::EndCycleCollectionCallback( const CycleCollectorResults& aResults) { MOZ_ASSERT(NS_IsMainThread()); sScheduler.KillCCRunner(); // Update timing information for the current slice before we log it, if // we previously called PrepareForCycleCollectionSlice(). During shutdown // CCs, this won't happen. sCCStats.AfterCycleCollectionSlice(); TimeStamp endCCTimeStamp = TimeStamp::Now(); TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp); TimeStamp prevCCEnd = sScheduler.GetLastCCEndTime(); sScheduler.NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime); // Log information about the CC via telemetry, JSON and the console. sCCStats.SendTelemetry(ccNowDuration, prevCCEnd); uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u); sCCStats.MaybeLogStats(aResults, cleanups); sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups); // Update global state to indicate we have just run a cycle collection. sCCStats.Clear(); // If we need a GC after this CC (typically because lots of GCed objects or // zones have been collected in the CC), schedule it. if (sScheduler.NeedsGCAfterCC()) { MOZ_ASSERT( TimeDuration::FromMilliseconds( StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0"); sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr, TimeDuration::FromMilliseconds( StaticPrefs::javascript_options_gc_delay()) - std::min(ccNowDuration, kMaxICCDuration)); } } /* static */ bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) { AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC); bool didDoWork = false; // The CC/GC scheduler (sScheduler) decides what action(s) to take during // this invocation of the CC runner. // // This may be zero, one, or multiple actions. (Zero is when CC is blocked by // incremental GC, or when the scheduler determined that a CC is no longer // needed.) Loop until the scheduler finishes this invocation by returning // `Yield` in step.mYield. CCRunnerStep step; do { step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(), nsCycleCollector_suspectedCount()); switch (step.mAction) { case CCRunnerAction::None: break; case CCRunnerAction::MinorGC: JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(), step.mParam.mReason); sScheduler.NoteMinorGCEnd(); break; case CCRunnerAction::ForgetSkippable: // 'Forget skippable' only, then end this invocation. FireForgetSkippable(bool(step.mParam.mRemoveChildless), aDeadline); break; case CCRunnerAction::CleanupContentUnbinder: // Clear content unbinder before the first actual CC slice. Element::ClearContentUnbinder(); break; case CCRunnerAction::CleanupDeferred: // and if time still permits, perform deferred deletions. nsCycleCollector_doDeferredDeletion(); break; case CCRunnerAction::CycleCollect: // Cycle collection slice. nsJSContext::RunCycleCollectorSlice(step.mParam.mCCReason, aDeadline); break; case CCRunnerAction::StopRunning: // End this CC, either because we have run a cycle collection slice, or // because a CC is no longer needed. sScheduler.KillCCRunner(); break; } if (step.mAction != CCRunnerAction::None) { didDoWork = true; } } while (step.mYield == CCRunnerYield::Continue); return didDoWork; } // static bool nsJSContext::HasHadCleanupSinceLastGC() { return sScheduler.IsEarlyForgetSkippable(1); } // static void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason, mozilla::TimeStamp aDeadline) { sScheduler.RunNextCollectorTimer(aReason, aDeadline); } // static void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell, JS::GCReason aReason) { if (!aDocShell || !XRE_IsContentProcess()) { return; } BrowsingContext* bc = aDocShell->GetBrowsingContext(); if (!bc) { return; } BrowsingContext* root = bc->Top(); if (bc == root) { // We don't want to run collectors when loading the top level page. return; } nsIDocShell* rootDocShell = root->GetDocShell(); if (!rootDocShell) { return; } Document* rootDocument = rootDocShell->GetDocument(); if (!rootDocument || rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE || rootDocument->IsInBackgroundWindow()) { return; } PresShell* presShell = rootDocument->GetPresShell(); if (!presShell) { return; } nsViewManager* vm = presShell->GetViewManager(); if (!vm) { return; } if (!sScheduler.IsUserActive()) { Maybe next = nsRefreshDriver::GetNextTickHint(); // Try to not delay the next RefreshDriver tick, so give a reasonable // deadline for collectors. if (next.isSome()) { sScheduler.RunNextCollectorTimer(aReason, next.value()); } } } // static void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj, TimeDuration aDelay) { sScheduler.PokeGC(aReason, aObj, aDelay); } // static void nsJSContext::MaybePokeGC() { if (sShuttingDown) { return; } JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime(); JS::GCReason reason = JS::WantEagerMinorGC(rt); if (reason != JS::GCReason::NO_REASON) { MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION); sScheduler.PokeMinorGC(reason); } // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some // benchmarks. Hopefully that will be worked out and this will check for // whether an eager major GC is needed. } void nsJSContext::DoLowMemoryGC() { if (sShuttingDown) { return; } nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE, nsJSContext::ShrinkingGC); nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE); if (sScheduler.NeedsGCAfterCC()) { nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE, nsJSContext::ShrinkingGC); } } // static void nsJSContext::LowMemoryGC() { RefPtr mbPromise = CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE); if (!mbPromise) { // Normally when the promise is null it means that IPC failed, that probably // means that something bad happened, don't bother with the GC. return; } mbPromise->Then( GetMainThreadSerialEventTarget(), __func__, [](bool aIgnored) { DoLowMemoryGC(); }, [](mozilla::ipc::ResponseRejectReason r) {}); } // static void nsJSContext::MaybePokeCC() { sScheduler.MaybePokeCC(TimeStamp::Now(), nsCycleCollector_suspectedCount()); } static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescription& aDesc) { NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread"); static TimeStamp sCurrentGCStartTime; switch (aProgress) { case JS::GC_CYCLE_BEGIN: { // Prevent cycle collections and shrinking during incremental GC. sScheduler.NoteGCBegin(aDesc.reason_); sCurrentGCStartTime = TimeStamp::Now(); break; } case JS::GC_CYCLE_END: { TimeDuration delta = GetCollectionTimeDelta(); if (StaticPrefs::javascript_options_mem_log()) { nsString gcstats; gcstats.Adopt(aDesc.formatSummaryMessage(aCx)); nsAutoString prefix; nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ", delta.ToSeconds(), ProcessNameForCollectorLog(), getpid()); nsString msg = prefix + gcstats; nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { cs->LogStringMessage(msg.get()); } } sScheduler.NoteGCEnd(); // May need to kill the GC runner sScheduler.KillGCRunner(); nsJSContext::MaybePokeCC(); if (aDesc.isZone_) { sScheduler.PokeFullGC(); } else { sScheduler.SetNeedsFullGC(false); sScheduler.KillFullGCTimer(); } if (sScheduler.IsCCNeeded(TimeStamp::Now(), nsCycleCollector_suspectedCount()) != CCReason::NO_REASON) { nsCycleCollector_dispatchDeferredDeletion(); } Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS, TimeUntilNow(sCurrentGCStartTime).ToMilliseconds()); break; } case JS::GC_SLICE_BEGIN: break; case JS::GC_SLICE_END: sScheduler.NoteGCSliceEnd(aDesc.lastSliceStart(aCx), aDesc.lastSliceEnd(aCx)); if (sShuttingDown) { sScheduler.KillGCRunner(); } else { // If incremental GC wasn't triggered by GCTimerFired, we may not have a // runner to ensure all the slices are handled. So, create the runner // here. sScheduler.EnsureGCRunner(0); } if (sScheduler.IsCCNeeded(TimeStamp::Now(), nsCycleCollector_suspectedCount()) != CCReason::NO_REASON) { nsCycleCollector_dispatchDeferredDeletion(); } if (StaticPrefs::javascript_options_mem_log()) { nsString gcstats; gcstats.Adopt(aDesc.formatSliceMessage(aCx)); nsAutoString prefix; nsTextFormatter::ssprintf(prefix, u"[%s-%i] ", ProcessNameForCollectorLog(), getpid()); nsString msg = prefix + gcstats; nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { cs->LogStringMessage(msg.get()); } } break; default: MOZ_CRASH("Unexpected GCProgress value"); } if (sPrevGCSliceCallback) { (*sPrevGCSliceCallback)(aCx, aProgress, aDesc); } } void nsJSContext::SetWindowProxy(JS::Handle aWindowProxy) { mWindowProxy = aWindowProxy; } JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; } void nsJSContext::LikelyShortLivingObjectCreated() { ++sScheduler.mLikelyShortLivingObjectsNeedingGC; } void mozilla::dom::StartupJSEnvironment() { // initialize all our statics, so that we can restart XPCOM sIsInitialized = false; sShuttingDown = false; new (&sScheduler) CCGCScheduler(); // Reset the scheduler state. sCCStats.Init(); } static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) { AutoJSAPI jsapi; jsapi.Init(); JS_SetGCParameter(jsapi.cx(), aParam, aValue); } static void ResetGCParameter(JSGCParamKey aParam) { AutoJSAPI jsapi; jsapi.Init(); JS_ResetGCParameter(jsapi.cx(), aParam); } static void SetMemoryPrefChangedCallbackMB(const char* aPrefName, void* aClosure) { int32_t prefMB = Preferences::GetInt(aPrefName, -1); // handle overflow and negative pref values CheckedInt prefB = CheckedInt(prefMB) * 1024 * 1024; if (prefB.isValid() && prefB.value() >= 0) { SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value()); } else { ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure); } } static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName, void* aClosure) { int32_t prefKB = Preferences::GetInt(aPrefName, -1); // handle overflow and negative pref values CheckedInt prefB = CheckedInt(prefKB) * 1024; if (prefB.isValid() && prefB.value() >= 0) { SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value()); } else { ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure); } } static void SetMemoryPrefChangedCallbackInt(const char* aPrefName, void* aClosure) { int32_t pref = Preferences::GetInt(aPrefName, -1); // handle overflow and negative pref values if (pref >= 0 && pref < 10000) { SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref); } else { ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure); } } static void SetMemoryPrefChangedCallbackBool(const char* aPrefName, void* aClosure) { bool pref = Preferences::GetBool(aPrefName); SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref); } static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName, void* aClosure) { int32_t pref = Preferences::GetInt(aPrefName, -1); // handle overflow and negative pref values if (pref > 0 && pref < 100000) { sScheduler.SetActiveIntersliceGCBudget( TimeDuration::FromMilliseconds(pref)); SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref); } else { ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS); } } static void SetIncrementalCCPrefChangedCallback(const char* aPrefName, void* aClosure) { bool pref = Preferences::GetBool(aPrefName); sIncrementalCC = pref; } class JSDispatchableRunnable final : public Runnable { ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); } public: explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable) : mozilla::Runnable("JSDispatchableRunnable"), mDispatchable(aDispatchable) { MOZ_ASSERT(mDispatchable); } protected: NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); AutoJSAPI jsapi; jsapi.Init(); JS::Dispatchable::MaybeShuttingDown maybeShuttingDown = sShuttingDown ? JS::Dispatchable::ShuttingDown : JS::Dispatchable::NotShuttingDown; mDispatchable->run(jsapi.cx(), maybeShuttingDown); mDispatchable = nullptr; // mDispatchable may delete itself return NS_OK; } private: JS::Dispatchable* mDispatchable; }; static bool DispatchToEventLoop(void* closure, JS::Dispatchable* aDispatchable) { MOZ_ASSERT(!closure); // This callback may execute either on the main thread or a random JS-internal // helper thread. This callback can be called during shutdown so we cannot // simply NS_DispatchToMainThread. Failure during shutdown is expected and // properly handled by the JS engine. nsCOMPtr mainTarget = GetMainThreadEventTarget(); if (!mainTarget) { return false; } RefPtr r = new JSDispatchableRunnable(aDispatchable); MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); return true; } static bool ConsumeStream(JSContext* aCx, JS::Handle aObj, JS::MimeType aMimeType, JS::StreamConsumer* aConsumer) { return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer, nullptr); } static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason, int64_t aMillis) { return sScheduler.CreateGCSliceBudget( mozilla::TimeDuration::FromMilliseconds(aMillis), false, false); } void nsJSContext::EnsureStatics() { if (sIsInitialized) { if (!nsContentUtils::XPConnect()) { MOZ_CRASH(); } return; } // Let's make sure that our main thread is the same as the xpcom main thread. MOZ_ASSERT(NS_IsMainThread()); AutoJSAPI jsapi; jsapi.Init(); sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback); JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget); JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr); JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream, FetchUtil::ReportJSStreamError); // Set these global xpconnect options... Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB, "javascript.options.mem.max", (void*)JSGC_MAX_BYTES); Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback, "javascript.options.mem.nursery.min_kb", (void*)JSGC_MIN_NURSERY_BYTES); Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback, "javascript.options.mem.nursery.max_kb", (void*)JSGC_MAX_NURSERY_BYTES); Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool, "javascript.options.mem.gc_per_zone", (void*)JSGC_PER_ZONE_GC_ENABLED); Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool, "javascript.options.mem.gc_incremental", (void*)JSGC_INCREMENTAL_GC_ENABLED); Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool, "javascript.options.mem.gc_compacting", (void*)JSGC_COMPACTING_ENABLED); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackBool, "javascript.options.mem.gc_parallel_marking", (void*)JSGC_PARALLEL_MARKING_ENABLED); Preferences::RegisterCallbackAndCall( SetMemoryGCSliceTimePrefChangedCallback, "javascript.options.mem.gc_incremental_slice_ms"); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackBool, "javascript.options.mem.incremental_weakmap", (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_high_frequency_time_limit_ms", (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_low_frequency_heap_growth", (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_high_frequency_large_heap_growth", (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_high_frequency_small_heap_growth", (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackBool, "javascript.options.mem.gc_balanced_heap_limits", (void*)JSGC_BALANCED_HEAP_LIMITS_ENABLED); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_heap_growth_factor", (void*)JSGC_HEAP_GROWTH_FACTOR); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_small_heap_size_max_mb", (void*)JSGC_SMALL_HEAP_SIZE_MAX); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_large_heap_size_min_mb", (void*)JSGC_LARGE_HEAP_SIZE_MIN); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_allocation_threshold_mb", (void*)JSGC_ALLOCATION_THRESHOLD); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_malloc_threshold_base_mb", (void*)JSGC_MALLOC_THRESHOLD_BASE); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_small_heap_incremental_limit", (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_large_heap_incremental_limit", (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_urgent_threshold_mb", (void*)JSGC_URGENT_THRESHOLD_MB); Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback, "dom.cycle_collector.incremental"); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_min_empty_chunk_count", (void*)JSGC_MIN_EMPTY_CHUNK_COUNT); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_max_empty_chunk_count", (void*)JSGC_MAX_EMPTY_CHUNK_COUNT); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_helper_thread_ratio", (void*)JSGC_HELPER_THREAD_RATIO); Preferences::RegisterCallbackAndCall( SetMemoryPrefChangedCallbackInt, "javascript.options.mem.gc_max_helper_threads", (void*)JSGC_MAX_HELPER_THREADS); nsCOMPtr obs = mozilla::services::GetObserverService(); if (!obs) { MOZ_CRASH(); } nsIObserver* observer = new nsJSEnvironmentObserver(); obs->AddObserver(observer, "memory-pressure", false); obs->AddObserver(observer, "user-interaction-inactive", false); obs->AddObserver(observer, "user-interaction-active", false); obs->AddObserver(observer, "quit-application", false); obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); obs->AddObserver(observer, "content-child-will-shutdown", false); sIsInitialized = true; } void mozilla::dom::ShutdownJSEnvironment() { sShuttingDown = true; sScheduler.Shutdown(); } AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport) : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {} void AsyncErrorReporter::SerializeStack(JSContext* aCx, JS::Handle aStack) { mStackHolder = MakeUnique(); mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack); } void AsyncErrorReporter::SetException(JSContext* aCx, JS::Handle aException) { MOZ_ASSERT(NS_IsMainThread()); mException.init(aCx, aException); mHasException = true; } NS_IMETHODIMP AsyncErrorReporter::Run() { AutoJSAPI jsapi; // We're only using this context to deserialize a stack to report to the // console, so the scope we use doesn't matter. Stack frame filtering happens // based on the principal encoded into the frame and the caller compartment, // not the compartment of the frame object, and the console reporting code // will not be using our context, and therefore will not care what compartment // it has entered. DebugOnly ok = jsapi.Init(xpc::PrivilegedJunkScope()); MOZ_ASSERT(ok, "Problem with system global?"); JSContext* cx = jsapi.cx(); JS::Rooted stack(cx); JS::Rooted stackGlobal(cx); if (mStackHolder) { stack = mStackHolder->ReadStack(cx); if (stack) { stackGlobal = JS::CurrentGlobalOrNull(cx); } } JS::Rooted> exception(cx, Nothing()); if (mHasException) { MOZ_ASSERT(NS_IsMainThread()); exception = Some(mException); // Remove our reference to the exception. mException.setUndefined(); mHasException = false; } mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal); return NS_OK; } // A fast-array class for JS. This class supports both nsIJSScriptArray and // nsIArray. If it is JS itself providing and consuming this class, all work // can be done via nsIJSScriptArray, and avoid the conversion of elements // to/from nsISupports. // When consumed by non-JS (eg, another script language), conversion is done // on-the-fly. class nsJSArgArray final : public nsIJSArgArray { public: nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv, nsresult* prv); // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray, nsIJSArgArray) // nsIArray NS_DECL_NSIARRAY // nsIJSArgArray nsresult GetArgs(uint32_t* argc, void** argv) override; void ReleaseJSObjects(); protected: ~nsJSArgArray(); JSContext* mContext; JS::Heap* mArgv; uint32_t mArgc; }; nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv, nsresult* prv) : mContext(aContext), mArgv(nullptr), mArgc(argc) { // copy the array - we don't know its lifetime, and ours is tied to xpcom // refcounting. if (argc) { mArgv = new (fallible) JS::Heap[argc]; if (!mArgv) { *prv = NS_ERROR_OUT_OF_MEMORY; return; } } // Callers are allowed to pass in a null argv even for argc > 0. They can // then use GetArgs to initialize the values. if (argv) { for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i]; } if (argc > 0) { mozilla::HoldJSObjects(this); } *prv = NS_OK; } nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); } void nsJSArgArray::ReleaseJSObjects() { delete[] mArgv; if (mArgc > 0) { mArgc = 0; mozilla::DropJSObjects(this); } } // QueryInterface implementation for nsJSArgArray NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray) tmp->ReleaseJSObjects(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray) if (tmp->mArgv) { for (uint32_t i = 0; i < tmp->mArgc; ++i) { NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i]) } } NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray) NS_INTERFACE_MAP_ENTRY(nsIArray) NS_INTERFACE_MAP_ENTRY(nsIJSArgArray) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray) nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) { *argv = (void*)mArgv; *argc = mArgc; return NS_OK; } // nsIArray impl NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) { *aLength = mArgc; return NS_OK; } NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid, void** result) { *result = nullptr; if (index >= mArgc) return NS_ERROR_INVALID_ARG; if (uuid.Equals(NS_GET_IID(nsIVariant)) || uuid.Equals(NS_GET_IID(nsISupports))) { // Have to copy a Heap into a Rooted to work with it. JS::Rooted val(mContext, mArgv[index]); return nsContentUtils::XPConnect()->JSToVariant(mContext, val, (nsIVariant**)result); } NS_WARNING("nsJSArgArray only handles nsIVariant"); return NS_ERROR_NO_INTERFACE; } NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element, uint32_t* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID, uint8_t aArgc, nsISimpleEnumerator** aResult) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID, nsISimpleEnumerator** _retval) { return NS_ERROR_NOT_IMPLEMENTED; } // The factory function nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc, const JS::Value* argv, nsIJSArgArray** aArray) { nsresult rv; nsCOMPtr ret = new nsJSArgArray(aContext, argc, argv, &rv); if (NS_FAILED(rv)) { return rv; } ret.forget(aArray); return NS_OK; }