Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / base / nsJSEnvironment.cpp
blobcceff7927815a0cc8d06acd4fc98a8832c597ebf
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsError.h"
8 #include "nsJSEnvironment.h"
9 #include "nsIScriptGlobalObject.h"
10 #include "nsIScriptObjectPrincipal.h"
11 #include "nsPIDOMWindow.h"
12 #include "nsDOMCID.h"
13 #include "nsIXPConnect.h"
14 #include "nsCOMPtr.h"
15 #include "nsISupportsPrimitives.h"
16 #include "nsReadableUtils.h"
17 #include "nsDOMJSUtils.h"
18 #include "nsJSUtils.h"
19 #include "nsIDocShell.h"
20 #include "nsIDocShellTreeItem.h"
21 #include "nsPresContext.h"
22 #include "nsIConsoleService.h"
23 #include "nsIInterfaceRequestor.h"
24 #include "nsIInterfaceRequestorUtils.h"
25 #include "nsIObserverService.h"
26 #include "nsITimer.h"
27 #include "nsAtom.h"
28 #include "nsContentUtils.h"
29 #include "mozilla/EventDispatcher.h"
30 #include "mozilla/HoldDropJSObjects.h"
31 #include "nsIContent.h"
32 #include "nsCycleCollector.h"
33 #include "nsXPCOMCIDInternal.h"
34 #include "nsServiceManagerUtils.h"
35 #include "nsTextFormatter.h"
36 #ifdef XP_WIN
37 # include <process.h>
38 # define getpid _getpid
39 #else
40 # include <unistd.h> // for getpid()
41 #endif
42 #include "xpcpublic.h"
44 #include "jsapi.h"
45 #include "js/Array.h" // JS::NewArrayObject
46 #include "js/PropertyAndElement.h" // JS_DefineProperty
47 #include "js/PropertySpec.h"
48 #include "js/SliceBudget.h"
49 #include "js/Wrapper.h"
50 #include "nsIArray.h"
51 #include "CCGCScheduler.h"
52 #include "WrapperFactory.h"
53 #include "nsGlobalWindowInner.h"
54 #include "nsGlobalWindowOuter.h"
55 #include "mozilla/AutoRestore.h"
56 #include "mozilla/BasePrincipal.h"
57 #include "mozilla/PresShell.h"
58 #include "mozilla/SchedulerGroup.h"
59 #include "mozilla/StaticPrefs_dom.h"
60 #include "mozilla/StaticPrefs_javascript.h"
61 #include "mozilla/StaticPtr.h"
62 #include "mozilla/dom/BrowsingContext.h"
63 #include "mozilla/dom/DOMException.h"
64 #include "mozilla/dom/DOMExceptionBinding.h"
65 #include "mozilla/dom/Element.h"
66 #include "mozilla/dom/ErrorEvent.h"
67 #include "mozilla/dom/FetchUtil.h"
68 #include "mozilla/dom/RootedDictionary.h"
69 #include "mozilla/dom/ScriptSettings.h"
70 #include "mozilla/dom/SerializedStackHolder.h"
71 #include "mozilla/CycleCollectedJSRuntime.h"
72 #include "nsRefreshDriver.h"
73 #include "nsJSPrincipals.h"
74 #include "AccessCheck.h"
75 #include "mozilla/Logging.h"
76 #include "prthread.h"
78 #include "mozilla/Preferences.h"
79 #include "mozilla/Telemetry.h"
80 #include "mozilla/dom/BindingUtils.h"
81 #include "mozilla/Attributes.h"
82 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
83 #include "mozilla/ContentEvents.h"
84 #include "mozilla/CycleCollectedJSContext.h"
85 #include "nsCycleCollectionNoteRootCallback.h"
86 #include "nsViewManager.h"
87 #include "mozilla/EventStateManager.h"
88 #include "mozilla/ProfilerLabels.h"
89 #include "mozilla/ProfilerMarkers.h"
90 #if defined(MOZ_MEMORY)
91 # include "mozmemory.h"
92 #endif
94 using namespace mozilla;
95 using namespace mozilla::dom;
97 // Thank you Microsoft!
98 #ifdef CompareString
99 # undef CompareString
100 #endif
102 static JS::GCSliceCallback sPrevGCSliceCallback;
104 static bool sIncrementalCC = false;
106 static bool sIsInitialized;
107 static bool sShuttingDown;
109 static CCGCScheduler sScheduler;
111 struct CycleCollectorStats {
112 constexpr CycleCollectorStats() = default;
113 void Init();
114 void Clear();
115 void PrepareForCycleCollection(TimeStamp aNow);
116 void AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline,
117 TimeStamp aBeginTime,
118 TimeStamp aMaybeAfterGCTime);
119 void AfterCycleCollectionSlice();
120 void AfterSyncForgetSkippable(TimeStamp beginTime);
121 void AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples);
122 void AfterCycleCollection();
124 void SendTelemetry(TimeDuration aCCNowDuration, TimeStamp aPrevCCEnd) const;
125 void MaybeLogStats(const CycleCollectorResults& aResults,
126 uint32_t aCleanups) const;
127 void MaybeNotifyStats(const CycleCollectorResults& aResults,
128 TimeDuration aCCNowDuration, uint32_t aCleanups) const;
130 // Time the current slice began, including any GC finishing.
131 TimeStamp mBeginSliceTime;
133 // Time the previous slice of the current CC ended.
134 TimeStamp mEndSliceTime;
136 // Time the current cycle collection began.
137 TimeStamp mBeginTime;
139 // The longest GC finishing duration for any slice of the current CC.
140 TimeDuration mMaxGCDuration;
142 // True if we ran sync forget skippable in any slice of the current CC.
143 bool mRanSyncForgetSkippable = false;
145 // Number of suspected objects at the start of the current CC.
146 uint32_t mSuspected = 0;
148 // The longest duration spent on sync forget skippable in any slice of the
149 // current CC.
150 TimeDuration mMaxSkippableDuration;
152 // The longest pause of any slice in the current CC.
153 TimeDuration mMaxSliceTime;
155 // The longest slice time since ClearMaxCCSliceTime() was called.
156 TimeDuration mMaxSliceTimeSinceClear;
158 // The total amount of time spent actually running the current CC.
159 TimeDuration mTotalSliceTime;
161 // True if we were locked out by the GC in any slice of the current CC.
162 bool mAnyLockedOut = false;
164 // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
165 FILE* mFile = nullptr;
167 // In case CC slice was triggered during idle time, set to the end of the idle
168 // period.
169 TimeStamp mIdleDeadline;
171 TimeDuration mMinForgetSkippableTime;
172 TimeDuration mMaxForgetSkippableTime;
173 TimeDuration mTotalForgetSkippableTime;
174 uint32_t mForgetSkippableBeforeCC = 0;
176 uint32_t mRemovedPurples = 0;
179 static CycleCollectorStats sCCStats;
181 static const char* ProcessNameForCollectorLog() {
182 return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
183 : "content";
186 namespace xpc {
188 // This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
189 // Exceptions, and arbitrary values that were associated with a stack by the
190 // JS engine when they were thrown, as specified by exceptionStack.
192 // Note that the returned stackObj and stackGlobal are _not_ wrapped into the
193 // compartment of exceptionValue.
194 void FindExceptionStackForConsoleReport(
195 nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue,
196 JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj,
197 JS::MutableHandle<JSObject*> stackGlobal) {
198 stackObj.set(nullptr);
199 stackGlobal.set(nullptr);
201 if (!exceptionValue.isObject()) {
202 // Use the stack provided by the JS engine, if available. This will not be
203 // a wrapper.
204 if (exceptionStack) {
205 stackObj.set(exceptionStack);
206 stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
208 return;
211 if (win && win->AsGlobal()->IsDying()) {
212 // Pretend like we have no stack, so we don't end up keeping the global
213 // alive via the stack.
214 return;
217 JS::RootingContext* rcx = RootingCx();
218 JS::Rooted<JSObject*> exceptionObject(rcx, &exceptionValue.toObject());
219 if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
220 // At this point we know exceptionObject is a possibly-wrapped
221 // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
222 // but excStack must be same-compartment with the unwrapped ErrorObject.
223 // Return the ErrorObject's global as stackGlobal. This matches what we do
224 // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
225 // are same-compartment.
226 JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
227 stackObj.set(excStack);
228 stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
229 return;
232 // It is not a JS Exception, try DOM Exception.
233 RefPtr<Exception> exception;
234 UNWRAP_OBJECT(DOMException, exceptionObject, exception);
235 if (!exception) {
236 // Not a DOM Exception, try XPC Exception.
237 UNWRAP_OBJECT(Exception, exceptionObject, exception);
238 if (!exception) {
239 // As above, use the stack provided by the JS engine, if available.
240 if (exceptionStack) {
241 stackObj.set(exceptionStack);
242 stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
244 return;
248 nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
249 if (!stack) {
250 return;
252 JS::Rooted<JS::Value> value(rcx);
253 stack->GetNativeSavedFrame(&value);
254 if (value.isObject()) {
255 stackObj.set(&value.toObject());
256 MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
257 stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
258 return;
262 } /* namespace xpc */
264 static TimeDuration GetCollectionTimeDelta() {
265 static TimeStamp sFirstCollectionTime;
266 TimeStamp now = TimeStamp::Now();
267 if (sFirstCollectionTime) {
268 return now - sFirstCollectionTime;
270 sFirstCollectionTime = now;
271 return TimeDuration();
274 class nsJSEnvironmentObserver final : public nsIObserver {
275 ~nsJSEnvironmentObserver() = default;
277 public:
278 NS_DECL_ISUPPORTS
279 NS_DECL_NSIOBSERVER
282 NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
284 NS_IMETHODIMP
285 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
286 const char16_t* aData) {
287 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
288 if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
289 if (sShuttingDown) {
290 // Don't GC/CC if we're already shutting down.
291 return NS_OK;
293 nsDependentString data(aData);
294 if (data.EqualsLiteral("low-memory-ongoing")) {
295 // Don't GC/CC if we are in an ongoing low-memory state since its very
296 // slow and it likely won't help us anyway.
297 return NS_OK;
299 if (data.EqualsLiteral("heap-minimize")) {
300 // heap-minimize notifiers expect this to run synchronously
301 nsJSContext::DoLowMemoryGC();
302 return NS_OK;
304 if (data.EqualsLiteral("low-memory")) {
305 nsJSContext::SetLowMemoryState(true);
307 // Asynchronously GC.
308 nsJSContext::LowMemoryGC();
310 } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
311 nsJSContext::SetLowMemoryState(false);
312 } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
313 sScheduler.UserIsInactive();
314 } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
315 sScheduler.UserIsActive();
316 } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
317 !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
318 !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
319 sShuttingDown = true;
320 sScheduler.Shutdown();
323 return NS_OK;
326 /****************************************************************
327 ************************** AutoFree ****************************
328 ****************************************************************/
330 class AutoFree {
331 public:
332 explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
333 ~AutoFree() {
334 if (mPtr) free(mPtr);
336 void Invalidate() { mPtr = nullptr; }
338 private:
339 void* mPtr;
342 // A utility function for script languages to call. Although it looks small,
343 // the use of nsIDocShell and nsPresContext triggers a huge number of
344 // dependencies that most languages would not otherwise need.
345 // XXXmarkh - This function is mis-placed!
346 bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
347 const ErrorEventInit& aErrorEventInit,
348 nsEventStatus* aStatus) {
349 bool called = false;
350 nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
351 nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
352 if (docShell) {
353 RefPtr<nsPresContext> presContext = docShell->GetPresContext();
355 static int32_t errorDepth; // Recursion prevention
356 ++errorDepth;
358 if (errorDepth < 2) {
359 // Dispatch() must be synchronous for the recursion block
360 // (errorDepth) to work.
361 RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
362 nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
363 event->SetTrusted(true);
365 // MOZ_KnownLive due to bug 1506441
366 EventDispatcher::DispatchDOMEvent(
367 MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
368 presContext, aStatus);
369 called = true;
371 --errorDepth;
373 return called;
376 class ScriptErrorEvent : public Runnable {
377 public:
378 ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
379 xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
380 JS::Handle<JSObject*> aErrorStack)
381 : mozilla::Runnable("ScriptErrorEvent"),
382 mWindow(aWindow),
383 mReport(aReport),
384 mError(aRootingCx, aError),
385 mErrorStack(aRootingCx, aErrorStack) {}
387 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
388 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
389 nsEventStatus status = nsEventStatus_eIgnore;
390 nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
391 MOZ_ASSERT(win);
392 MOZ_ASSERT(NS_IsMainThread());
393 // First, notify the DOM that we have a script error, but only if
394 // our window is still the current inner.
395 JS::RootingContext* rootingCx = RootingCx();
396 if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
397 !sHandlingScriptError) {
398 AutoRestore<bool> recursionGuard(sHandlingScriptError);
399 sHandlingScriptError = true;
401 RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
403 RootedDictionary<ErrorEventInit> init(rootingCx);
404 init.mCancelable = true;
405 init.mFilename = mReport->mFileName;
406 init.mBubbles = true;
408 constexpr auto xoriginMsg = u"Script error."_ns;
409 if (!mReport->mIsMuted) {
410 init.mMessage = mReport->mErrorMsg;
411 init.mLineno = mReport->mLineNumber;
412 init.mColno = mReport->mColumn;
413 init.mError = mError;
414 } else {
415 NS_WARNING("Not same origin error!");
416 init.mMessage = xoriginMsg;
417 init.mLineno = 0;
420 RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
421 nsGlobalWindowInner::Cast(win), u"error"_ns, init);
422 event->SetTrusted(true);
424 // MOZ_KnownLive due to bug 1506441
425 EventDispatcher::DispatchDOMEvent(
426 MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
427 presContext, &status);
430 if (status != nsEventStatus_eConsumeNoDefault) {
431 JS::Rooted<JSObject*> stack(rootingCx);
432 JS::Rooted<JSObject*> stackGlobal(rootingCx);
433 xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
434 &stackGlobal);
435 JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
436 nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
437 mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
440 return NS_OK;
443 private:
444 nsCOMPtr<nsPIDOMWindowInner> mWindow;
445 RefPtr<xpc::ErrorReport> mReport;
446 JS::PersistentRooted<JS::Value> mError;
447 JS::PersistentRooted<JSObject*> mErrorStack;
449 static bool sHandlingScriptError;
452 bool ScriptErrorEvent::sHandlingScriptError = false;
454 // This temporarily lives here to avoid code churn. It will go away entirely
455 // soon.
456 namespace xpc {
458 void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
459 JS::RootingContext* rootingCx,
460 xpc::ErrorReport* xpcReport,
461 JS::Handle<JS::Value> exception,
462 JS::Handle<JSObject*> exceptionStack) {
463 nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
464 win, rootingCx, xpcReport, exception, exceptionStack));
467 } /* namespace xpc */
469 #ifdef DEBUG
470 // A couple of useful functions to call when you're debugging.
471 nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
472 return xpc::WindowOrNull(obj);
475 template <typename T>
476 void PrintWinURI(T* win) {
477 if (!win) {
478 printf("No window passed in.\n");
479 return;
482 nsCOMPtr<Document> doc = win->GetExtantDoc();
483 if (!doc) {
484 printf("No document in the window.\n");
485 return;
488 nsIURI* uri = doc->GetDocumentURI();
489 if (!uri) {
490 printf("Document doesn't have a URI.\n");
491 return;
494 printf("%s\n", uri->GetSpecOrDefault().get());
497 void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
499 void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
501 template <typename T>
502 void PrintWinCodebase(T* win) {
503 if (!win) {
504 printf("No window passed in.\n");
505 return;
508 nsIPrincipal* prin = win->GetPrincipal();
509 if (!prin) {
510 printf("Window doesn't have principals.\n");
511 return;
513 if (prin->IsSystemPrincipal()) {
514 printf("No URI, it's the system principal.\n");
515 return;
517 nsCString spec;
518 prin->GetAsciiSpec(spec);
519 printf("%s\n", spec.get());
522 void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
523 return PrintWinCodebase(aWin);
526 void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
527 return PrintWinCodebase(aWin);
530 void DumpString(const nsAString& str) {
531 printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
533 #endif
535 nsJSContext::nsJSContext(bool aGCOnDestruction,
536 nsIScriptGlobalObject* aGlobalObject)
537 : mWindowProxy(nullptr),
538 mGCOnDestruction(aGCOnDestruction),
539 mGlobalObjectRef(aGlobalObject) {
540 EnsureStatics();
542 mProcessingScriptTag = false;
543 HoldJSObjects(this);
546 nsJSContext::~nsJSContext() {
547 mGlobalObjectRef = nullptr;
549 Destroy();
552 void nsJSContext::Destroy() {
553 if (mGCOnDestruction) {
554 sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
557 DropJSObjects(this);
560 // QueryInterface implementation for nsJSContext
561 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
563 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
564 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
565 NS_IMPL_CYCLE_COLLECTION_TRACE_END
567 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
568 tmp->mGCOnDestruction = false;
569 tmp->mWindowProxy = nullptr;
570 tmp->Destroy();
571 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
572 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
573 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
574 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
575 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
577 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
578 NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
579 NS_INTERFACE_MAP_ENTRY(nsISupports)
580 NS_INTERFACE_MAP_END
582 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
583 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
585 #ifdef DEBUG
586 bool AtomIsEventHandlerName(nsAtom* aName) {
587 const char16_t* name = aName->GetUTF16String();
589 const char16_t* cp;
590 char16_t c;
591 for (cp = name; *cp != '\0'; ++cp) {
592 c = *cp;
593 if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
596 return true;
598 #endif
600 nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
601 // Note: this could probably be simplified somewhat more; see bug 974327
602 // comments 1 and 3.
603 if (!mWindowProxy) {
604 return nullptr;
607 MOZ_ASSERT(mGlobalObjectRef);
608 return mGlobalObjectRef;
611 nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
612 const char* aPropName, nsISupports* aArgs) {
613 AutoJSAPI jsapi;
614 if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
615 return NS_ERROR_FAILURE;
617 JSContext* cx = jsapi.cx();
619 JS::RootedVector<JS::Value> args(cx);
621 JS::Rooted<JSObject*> global(cx, GetWindowProxy());
622 nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
623 NS_ENSURE_SUCCESS(rv, rv);
625 // got the arguments, now attach them.
627 for (uint32_t i = 0; i < args.length(); ++i) {
628 if (!JS_WrapValue(cx, args[i])) {
629 return NS_ERROR_FAILURE;
633 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
634 if (!array) {
635 return NS_ERROR_FAILURE;
638 return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
639 : NS_ERROR_FAILURE;
642 nsresult nsJSContext::ConvertSupportsTojsvals(
643 JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
644 JS::MutableHandleVector<JS::Value> aArgsOut) {
645 nsresult rv = NS_OK;
647 // If the array implements nsIJSArgArray, copy the contents and return.
648 nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
649 if (fastArray) {
650 uint32_t argc;
651 JS::Value* argv;
652 rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
653 if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
654 rv = NS_ERROR_OUT_OF_MEMORY;
656 return rv;
659 // Take the slower path converting each item.
660 // Handle only nsIArray and nsIVariant. nsIArray is only needed for
661 // SetProperty('arguments', ...);
663 nsIXPConnect* xpc = nsContentUtils::XPConnect();
664 NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
666 if (!aArgs) return NS_OK;
667 uint32_t argCount;
668 // This general purpose function may need to convert an arg array
669 // (window.arguments, event-handler args) and a generic property.
670 nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
672 if (argsArray) {
673 rv = argsArray->GetLength(&argCount);
674 NS_ENSURE_SUCCESS(rv, rv);
675 if (argCount == 0) return NS_OK;
676 } else {
677 argCount = 1; // the nsISupports which is not an array
680 // Use the caller's auto guards to release and unroot.
681 if (!aArgsOut.resize(argCount)) {
682 return NS_ERROR_OUT_OF_MEMORY;
685 if (argsArray) {
686 for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
687 nsCOMPtr<nsISupports> arg;
688 JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
689 argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
690 getter_AddRefs(arg));
691 if (!arg) {
692 thisVal.setNull();
693 continue;
695 nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
696 if (variant != nullptr) {
697 rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
698 } else {
699 // And finally, support the nsISupportsPrimitives supplied
700 // by the AppShell. It generally will pass only strings, but
701 // as we have code for handling all, we may as well use it.
702 rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
703 if (rv == NS_ERROR_NO_INTERFACE) {
704 // something else - probably an event object or similar -
705 // just wrap it.
706 #ifdef DEBUG
707 // but first, check its not another nsISupportsPrimitive, as
708 // these are now deprecated for use with script contexts.
709 nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
710 NS_ASSERTION(prim == nullptr,
711 "Don't pass nsISupportsPrimitives - use nsIVariant!");
712 #endif
713 JSAutoRealm ar(aCx, aScope);
714 rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
718 } else {
719 nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
720 if (variant) {
721 rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
722 } else {
723 NS_ERROR("Not an array, not an interface?");
724 rv = NS_ERROR_UNEXPECTED;
727 return rv;
730 // This really should go into xpconnect somewhere...
731 nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
732 nsISupports* aArg,
733 JS::Value* aArgv) {
734 MOZ_ASSERT(aArg, "Empty arg");
736 nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
737 if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
739 uint16_t type;
740 argPrimitive->GetType(&type);
742 switch (type) {
743 case nsISupportsPrimitive::TYPE_CSTRING: {
744 nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
745 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
747 nsAutoCString data;
749 p->GetData(data);
751 JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
752 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
754 aArgv->setString(str);
756 break;
758 case nsISupportsPrimitive::TYPE_STRING: {
759 nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
760 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
762 nsAutoString data;
764 p->GetData(data);
766 // cast is probably safe since wchar_t and char16_t are expected
767 // to be equivalent; both unsigned 16-bit entities
768 JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
769 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
771 aArgv->setString(str);
772 break;
774 case nsISupportsPrimitive::TYPE_PRBOOL: {
775 nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
776 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
778 bool data;
780 p->GetData(&data);
782 aArgv->setBoolean(data);
784 break;
786 case nsISupportsPrimitive::TYPE_PRUINT8: {
787 nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
788 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
790 uint8_t data;
792 p->GetData(&data);
794 aArgv->setInt32(data);
796 break;
798 case nsISupportsPrimitive::TYPE_PRUINT16: {
799 nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
800 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
802 uint16_t data;
804 p->GetData(&data);
806 aArgv->setInt32(data);
808 break;
810 case nsISupportsPrimitive::TYPE_PRUINT32: {
811 nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
812 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
814 uint32_t data;
816 p->GetData(&data);
818 aArgv->setInt32(data);
820 break;
822 case nsISupportsPrimitive::TYPE_CHAR: {
823 nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
824 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
826 char data;
828 p->GetData(&data);
830 JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
831 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
833 aArgv->setString(str);
835 break;
837 case nsISupportsPrimitive::TYPE_PRINT16: {
838 nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
839 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
841 int16_t data;
843 p->GetData(&data);
845 aArgv->setInt32(data);
847 break;
849 case nsISupportsPrimitive::TYPE_PRINT32: {
850 nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
851 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
853 int32_t data;
855 p->GetData(&data);
857 aArgv->setInt32(data);
859 break;
861 case nsISupportsPrimitive::TYPE_FLOAT: {
862 nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
863 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
865 float data;
867 p->GetData(&data);
869 *aArgv = ::JS_NumberValue(data);
871 break;
873 case nsISupportsPrimitive::TYPE_DOUBLE: {
874 nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
875 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
877 double data;
879 p->GetData(&data);
881 *aArgv = ::JS_NumberValue(data);
883 break;
885 case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
886 nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
887 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
889 nsCOMPtr<nsISupports> data;
890 nsIID* iid = nullptr;
892 p->GetData(getter_AddRefs(data));
893 p->GetDataIID(&iid);
894 NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
896 AutoFree iidGuard(iid); // Free iid upon destruction.
898 JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
899 JS::Rooted<JS::Value> v(aCx);
900 JSAutoRealm ar(aCx, scope);
901 nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
902 NS_ENSURE_SUCCESS(rv, rv);
904 *aArgv = v;
906 break;
908 case nsISupportsPrimitive::TYPE_ID:
909 case nsISupportsPrimitive::TYPE_PRUINT64:
910 case nsISupportsPrimitive::TYPE_PRINT64:
911 case nsISupportsPrimitive::TYPE_PRTIME: {
912 NS_WARNING("Unsupported primitive type used");
913 aArgv->setNull();
914 break;
916 default: {
917 NS_WARNING("Unknown primitive type used");
918 aArgv->setNull();
919 break;
922 return NS_OK;
925 #ifdef MOZ_JPROF
927 # include <signal.h>
929 inline bool IsJProfAction(struct sigaction* action) {
930 return (action->sa_sigaction &&
931 (action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
932 (SA_RESTART | SA_SIGINFO));
935 void NS_JProfStartProfiling();
936 void NS_JProfStopProfiling();
937 void NS_JProfClearCircular();
939 static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
940 NS_JProfStartProfiling();
941 return true;
944 void NS_JProfStartProfiling() {
945 // Figure out whether we're dealing with SIGPROF, SIGALRM, or
946 // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
947 // JP_RTC_HZ)
948 struct sigaction action;
950 // Must check ALRM before PROF since both are enabled for real-time
951 sigaction(SIGALRM, nullptr, &action);
952 // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
953 if (IsJProfAction(&action)) {
954 // printf("Beginning real-time jprof profiling.\n");
955 raise(SIGALRM);
956 return;
959 sigaction(SIGPROF, nullptr, &action);
960 // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
961 if (IsJProfAction(&action)) {
962 // printf("Beginning process-time jprof profiling.\n");
963 raise(SIGPROF);
964 return;
967 sigaction(SIGPOLL, nullptr, &action);
968 // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
969 if (IsJProfAction(&action)) {
970 // printf("Beginning rtc-based jprof profiling.\n");
971 raise(SIGPOLL);
972 return;
975 printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
978 static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
979 NS_JProfStopProfiling();
980 return true;
983 void NS_JProfStopProfiling() {
984 raise(SIGUSR1);
985 // printf("Stopped jprof profiling.\n");
988 static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
989 NS_JProfClearCircular();
990 return true;
993 void NS_JProfClearCircular() {
994 raise(SIGUSR2);
995 // printf("cleared jprof buffer\n");
998 static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
999 // Not ideal...
1000 NS_JProfStopProfiling();
1001 NS_JProfStartProfiling();
1002 return true;
1005 static const JSFunctionSpec JProfFunctions[] = {
1006 JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
1007 JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
1008 JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
1009 JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
1011 #endif /* defined(MOZ_JPROF) */
1013 nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
1014 AutoJSAPI jsapi;
1015 jsapi.Init();
1016 JSContext* cx = jsapi.cx();
1017 JSAutoRealm ar(cx, aGlobalObj);
1019 #ifdef MOZ_JPROF
1020 // Attempt to initialize JProf functions
1021 ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
1022 #endif
1024 return NS_OK;
1027 bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
1029 void nsJSContext::SetProcessingScriptTag(bool aFlag) {
1030 mProcessingScriptTag = aFlag;
1033 // static
1034 void nsJSContext::SetLowMemoryState(bool aState) {
1035 JSContext* cx = danger::GetJSContext();
1036 JS::SetLowMemoryState(cx, aState);
1039 static void GarbageCollectImpl(JS::GCReason aReason,
1040 nsJSContext::IsShrinking aShrinking,
1041 const js::SliceBudget& aBudget) {
1042 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
1043 "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
1045 bool wantIncremental = !aBudget.isUnlimited();
1047 // We use danger::GetJSContext() since AutoJSAPI will assert if the current
1048 // thread's context is null (such as during shutdown).
1049 JSContext* cx = danger::GetJSContext();
1051 if (!nsContentUtils::XPConnect() || !cx) {
1052 return;
1055 if (sScheduler.InIncrementalGC() && wantIncremental) {
1056 // We're in the middle of incremental GC. Do another slice.
1057 JS::PrepareForIncrementalGC(cx);
1058 JS::IncrementalGCSlice(cx, aReason, aBudget);
1059 return;
1062 JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
1063 ? JS::GCOptions::Shrink
1064 : JS::GCOptions::Normal;
1066 if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
1067 sScheduler.SetNeedsFullGC();
1070 if (sScheduler.NeedsFullGC()) {
1071 JS::PrepareForFullGC(cx);
1074 if (wantIncremental) {
1075 // Incremental GC slices will be triggered by the GC Runner. If one doesn't
1076 // already exist, create it in the GC_SLICE_END callback for the first
1077 // slice being executed here.
1078 JS::StartIncrementalGC(cx, options, aReason, aBudget);
1079 } else {
1080 JS::NonIncrementalGC(cx, options, aReason);
1084 // static
1085 void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
1086 IsShrinking aShrinking) {
1087 GarbageCollectImpl(aReason, aShrinking, js::SliceBudget::unlimited());
1090 // static
1091 void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
1092 IsShrinking aShrinking,
1093 js::SliceBudget& aBudget) {
1094 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
1095 GarbageCollectImpl(aReason, aShrinking, aBudget);
1098 static void FinishAnyIncrementalGC() {
1099 AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
1101 if (sScheduler.InIncrementalGC()) {
1102 AutoJSAPI jsapi;
1103 jsapi.Init();
1105 // We're in the middle of an incremental GC, so finish it.
1106 JS::PrepareForIncrementalGC(jsapi.cx());
1107 JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
1111 namespace geckoprofiler::markers {
1112 class CCSliceMarker {
1113 public:
1114 static constexpr Span<const char> MarkerTypeName() {
1115 return mozilla::MakeStringSpan("CCSlice");
1117 static void StreamJSONMarkerData(
1118 mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
1119 bool aIsDuringIdle) {
1120 aWriter.BoolProperty("idle", aIsDuringIdle);
1122 static MarkerSchema MarkerTypeDisplay() {
1123 using MS = MarkerSchema;
1124 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
1125 MS::Location::TimelineMemory};
1126 schema.SetAllLabels("{marker.name} (idle={marker.data.idle})");
1127 schema.AddKeyLabelFormat("idle", "Idle", MS::Format::Integer);
1128 return schema;
1131 } // namespace geckoprofiler::markers
1133 static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
1134 TimeStamp startTimeStamp = TimeStamp::Now();
1135 FinishAnyIncrementalGC();
1137 uint32_t suspectedBefore = nsCycleCollector_suspectedCount();
1138 js::SliceBudget budget =
1139 sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
1140 bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable();
1141 nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
1142 earlyForgetSkippable);
1143 TimeStamp now = TimeStamp::Now();
1144 uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete(
1145 now, suspectedBefore, nsCycleCollector_suspectedCount());
1147 TimeDuration duration = now - startTimeStamp;
1149 sCCStats.AfterForgetSkippable(duration, removedPurples);
1151 if (duration.ToSeconds()) {
1152 TimeDuration idleDuration;
1153 if (!aDeadline.IsNull()) {
1154 if (aDeadline < now) {
1155 // This slice overflowed the idle period.
1156 if (aDeadline > startTimeStamp) {
1157 idleDuration = aDeadline - startTimeStamp;
1159 } else {
1160 idleDuration = duration;
1164 uint32_t percent =
1165 uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1166 Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
1169 PROFILER_MARKER("ForgetSkippable", GCCC,
1170 MarkerTiming::IntervalUntilNowFrom(startTimeStamp),
1171 CCSliceMarker, !aDeadline.IsNull());
1174 MOZ_ALWAYS_INLINE
1175 static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
1176 MOZ_ASSERT(aEnd >= aStart);
1177 return aEnd - aStart;
1180 static TimeDuration TimeUntilNow(TimeStamp start) {
1181 if (start.IsNull()) {
1182 return TimeDuration();
1184 return TimeBetween(start, TimeStamp::Now());
1187 void CycleCollectorStats::Init() {
1188 Clear();
1190 char* env = getenv("MOZ_CCTIMER");
1191 if (!env) {
1192 return;
1194 if (strcmp(env, "none") == 0) {
1195 mFile = nullptr;
1196 } else if (strcmp(env, "stdout") == 0) {
1197 mFile = stdout;
1198 } else if (strcmp(env, "stderr") == 0) {
1199 mFile = stderr;
1200 } else {
1201 mFile = fopen(env, "a");
1202 if (!mFile) {
1203 MOZ_CRASH("Failed to open MOZ_CCTIMER log file.");
1208 void CycleCollectorStats::Clear() {
1209 if (mFile && mFile != stdout && mFile != stderr) {
1210 fclose(mFile);
1212 *this = CycleCollectorStats();
1215 void CycleCollectorStats::AfterCycleCollectionSlice() {
1216 if (mBeginSliceTime.IsNull()) {
1217 // We already called this method from EndCycleCollectionCallback for this
1218 // slice.
1219 return;
1222 mEndSliceTime = TimeStamp::Now();
1223 TimeDuration duration = mEndSliceTime - mBeginSliceTime;
1225 PROFILER_MARKER(
1226 "CCSlice", GCCC, MarkerTiming::Interval(mBeginSliceTime, mEndSliceTime),
1227 CCSliceMarker, !mIdleDeadline.IsNull() && mIdleDeadline >= mEndSliceTime);
1229 if (duration.ToSeconds()) {
1230 TimeDuration idleDuration;
1231 if (!mIdleDeadline.IsNull()) {
1232 if (mIdleDeadline < mEndSliceTime) {
1233 // This slice overflowed the idle period.
1234 if (mIdleDeadline > mBeginSliceTime) {
1235 idleDuration = mIdleDeadline - mBeginSliceTime;
1237 } else {
1238 idleDuration = duration;
1242 uint32_t percent =
1243 uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1244 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE,
1245 percent);
1248 TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
1249 mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
1250 mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
1251 mTotalSliceTime += sliceTime;
1252 mBeginSliceTime = TimeStamp();
1255 void CycleCollectorStats::PrepareForCycleCollection(TimeStamp aNow) {
1256 mBeginTime = aNow;
1257 mSuspected = nsCycleCollector_suspectedCount();
1260 void CycleCollectorStats::AfterPrepareForCycleCollectionSlice(
1261 TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime) {
1262 mBeginSliceTime = aBeginTime;
1263 mIdleDeadline = aDeadline;
1265 if (!aMaybeAfterGCTime.IsNull()) {
1266 mAnyLockedOut = true;
1267 mMaxGCDuration = std::max(mMaxGCDuration, aMaybeAfterGCTime - aBeginTime);
1271 void CycleCollectorStats::AfterSyncForgetSkippable(TimeStamp beginTime) {
1272 mMaxSkippableDuration =
1273 std::max(mMaxSkippableDuration, TimeUntilNow(beginTime));
1274 mRanSyncForgetSkippable = true;
1277 void CycleCollectorStats::AfterForgetSkippable(TimeDuration duration,
1278 uint32_t aRemovedPurples) {
1279 if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) {
1280 mMinForgetSkippableTime = duration;
1282 if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) {
1283 mMaxForgetSkippableTime = duration;
1285 mTotalForgetSkippableTime += duration;
1286 ++mForgetSkippableBeforeCC;
1288 mRemovedPurples += aRemovedPurples;
1291 void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration,
1292 TimeStamp aPrevCCEnd) const {
1293 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut);
1294 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE,
1295 mRanSyncForgetSkippable);
1296 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL,
1297 aCCNowDuration.ToMilliseconds());
1298 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE,
1299 mMaxSliceTime.ToMilliseconds());
1301 if (!aPrevCCEnd.IsNull()) {
1302 TimeDuration timeBetween = TimeBetween(aPrevCCEnd, mBeginTime);
1303 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN,
1304 timeBetween.ToSeconds());
1307 Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
1308 mMaxForgetSkippableTime.ToMilliseconds());
1311 void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
1312 uint32_t aCleanups) const {
1313 if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) {
1314 return;
1317 TimeDuration delta = GetCollectionTimeDelta();
1319 nsCString mergeMsg;
1320 if (aResults.mMergedZones) {
1321 mergeMsg.AssignLiteral(" merged");
1324 nsCString gcMsg;
1325 if (aResults.mForcedGC) {
1326 gcMsg.AssignLiteral(", forced a GC");
1329 const char16_t* kFmt =
1330 u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
1331 u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
1332 u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
1333 u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
1334 u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
1335 nsString msg;
1336 nsTextFormatter::ssprintf(
1337 msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
1338 ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
1339 mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
1340 aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
1341 aResults.mFreedRefCounted, aResults.mFreedGCed,
1342 sScheduler.mCCollectedWaitingForGC,
1343 sScheduler.mCCollectedZonesWaitingForGC,
1344 sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
1345 mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
1346 mMaxForgetSkippableTime.ToMilliseconds(),
1347 mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1348 mTotalForgetSkippableTime.ToMilliseconds(),
1349 mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples);
1350 if (StaticPrefs::javascript_options_mem_log()) {
1351 nsCOMPtr<nsIConsoleService> cs =
1352 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1353 if (cs) {
1354 cs->LogStringMessage(msg.get());
1357 if (mFile) {
1358 fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
1362 void CycleCollectorStats::MaybeNotifyStats(
1363 const CycleCollectorResults& aResults, TimeDuration aCCNowDuration,
1364 uint32_t aCleanups) const {
1365 if (!StaticPrefs::javascript_options_mem_notify()) {
1366 return;
1369 const char16_t* kJSONFmt =
1370 u"{ \"timestamp\": %llu, "
1371 u"\"duration\": %.f, "
1372 u"\"max_slice_pause\": %.f, "
1373 u"\"total_slice_pause\": %.f, "
1374 u"\"max_finish_gc_duration\": %.f, "
1375 u"\"max_sync_skippable_duration\": %.f, "
1376 u"\"suspected\": %lu, "
1377 u"\"visited\": { "
1378 u"\"RCed\": %lu, "
1379 u"\"GCed\": %lu }, "
1380 u"\"collected\": { "
1381 u"\"RCed\": %lu, "
1382 u"\"GCed\": %lu }, "
1383 u"\"waiting_for_gc\": %lu, "
1384 u"\"zones_waiting_for_gc\": %lu, "
1385 u"\"short_living_objects_waiting_for_gc\": %lu, "
1386 u"\"forced_gc\": %d, "
1387 u"\"forget_skippable\": { "
1388 u"\"times_before_cc\": %lu, "
1389 u"\"min\": %.f, "
1390 u"\"max\": %.f, "
1391 u"\"avg\": %.f, "
1392 u"\"total\": %.f, "
1393 u"\"removed\": %lu } "
1394 u"}";
1396 nsString json;
1397 nsTextFormatter::ssprintf(
1398 json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
1399 mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
1400 mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
1401 mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
1402 aResults.mFreedRefCounted, aResults.mFreedGCed,
1403 sScheduler.mCCollectedWaitingForGC,
1404 sScheduler.mCCollectedZonesWaitingForGC,
1405 sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
1406 mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
1407 mMaxForgetSkippableTime.ToMilliseconds(),
1408 mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1409 mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
1410 nsCOMPtr<nsIObserverService> observerService =
1411 mozilla::services::GetObserverService();
1412 if (observerService) {
1413 observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
1414 json.get());
1418 // static
1419 void nsJSContext::CycleCollectNow(CCReason aReason,
1420 nsICycleCollectorListener* aListener) {
1421 if (!NS_IsMainThread()) {
1422 return;
1425 AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
1427 PrepareForCycleCollectionSlice(aReason, TimeStamp());
1428 nsCycleCollector_collect(aReason, aListener);
1429 sCCStats.AfterCycleCollectionSlice();
1432 // static
1433 void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason,
1434 TimeStamp aDeadline) {
1435 TimeStamp beginTime = TimeStamp::Now();
1437 // Before we begin the cycle collection, make sure there is no active GC.
1438 TimeStamp afterGCTime;
1439 if (sScheduler.InIncrementalGC()) {
1440 FinishAnyIncrementalGC();
1441 afterGCTime = TimeStamp::Now();
1444 if (!sScheduler.IsCollectingCycles()) {
1445 sCCStats.PrepareForCycleCollection(beginTime);
1446 sScheduler.NoteCCBegin(aReason, beginTime,
1447 sCCStats.mForgetSkippableBeforeCC,
1448 sCCStats.mSuspected, sCCStats.mRemovedPurples);
1451 sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime,
1452 afterGCTime);
1455 // static
1456 void nsJSContext::RunCycleCollectorSlice(CCReason aReason,
1457 TimeStamp aDeadline) {
1458 if (!NS_IsMainThread()) {
1459 return;
1462 PrepareForCycleCollectionSlice(aReason, aDeadline);
1464 // Decide how long we want to budget for this slice.
1465 if (sIncrementalCC) {
1466 bool preferShorterSlices;
1467 js::SliceBudget budget = sScheduler.ComputeCCSliceBudget(
1468 aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime,
1469 TimeStamp::Now(), &preferShorterSlices);
1470 nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices);
1471 } else {
1472 js::SliceBudget budget = js::SliceBudget::unlimited();
1473 nsCycleCollector_collectSlice(budget, aReason, false);
1476 sCCStats.AfterCycleCollectionSlice();
1479 // static
1480 void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
1481 if (!NS_IsMainThread()) {
1482 return;
1485 AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
1487 PrepareForCycleCollectionSlice(CCReason::API, TimeStamp());
1489 js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
1490 nsCycleCollector_collectSlice(budget, CCReason::API);
1492 sCCStats.AfterCycleCollectionSlice();
1495 void nsJSContext::ClearMaxCCSliceTime() {
1496 sCCStats.mMaxSliceTimeSinceClear = TimeDuration();
1499 uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
1500 return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
1503 // static
1504 void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) {
1505 MOZ_ASSERT(NS_IsMainThread());
1507 TimeStamp startTime = TimeStamp::Now();
1508 sCCStats.PrepareForCycleCollection(startTime);
1510 // Run forgetSkippable synchronously to reduce the size of the CC graph. This
1511 // is particularly useful if we recently finished a GC.
1512 if (sScheduler.IsEarlyForgetSkippable()) {
1513 while (sScheduler.IsEarlyForgetSkippable()) {
1514 FireForgetSkippable(false, TimeStamp());
1516 sCCStats.AfterSyncForgetSkippable(startTime);
1519 if (sShuttingDown) {
1520 return;
1523 sScheduler.InitCCRunnerStateMachine(
1524 mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason);
1525 sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
1528 // static
1529 void nsJSContext::EndCycleCollectionCallback(
1530 const CycleCollectorResults& aResults) {
1531 MOZ_ASSERT(NS_IsMainThread());
1533 sScheduler.KillCCRunner();
1535 // Update timing information for the current slice before we log it, if
1536 // we previously called PrepareForCycleCollectionSlice(). During shutdown
1537 // CCs, this won't happen.
1538 sCCStats.AfterCycleCollectionSlice();
1540 TimeStamp endCCTimeStamp = TimeStamp::Now();
1541 TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
1542 TimeStamp prevCCEnd = sScheduler.GetLastCCEndTime();
1544 sScheduler.NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime);
1546 // Log information about the CC via telemetry, JSON and the console.
1548 sCCStats.SendTelemetry(ccNowDuration, prevCCEnd);
1550 uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u);
1552 sCCStats.MaybeLogStats(aResults, cleanups);
1554 sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups);
1556 // Update global state to indicate we have just run a cycle collection.
1557 sCCStats.Clear();
1559 // If we need a GC after this CC (typically because lots of GCed objects or
1560 // zones have been collected in the CC), schedule it.
1562 if (sScheduler.NeedsGCAfterCC()) {
1563 MOZ_ASSERT(
1564 TimeDuration::FromMilliseconds(
1565 StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration,
1566 "A max duration ICC shouldn't reduce GC delay to 0");
1568 sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr,
1569 TimeDuration::FromMilliseconds(
1570 StaticPrefs::javascript_options_gc_delay()) -
1571 std::min(ccNowDuration, kMaxICCDuration));
1573 #if defined(MOZ_MEMORY)
1574 else if (
1575 StaticPrefs::
1576 dom_memory_foreground_content_processes_have_larger_page_cache()) {
1577 jemalloc_free_dirty_pages();
1579 #endif
1582 /* static */
1583 bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) {
1584 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
1586 bool didDoWork = false;
1588 // The CC/GC scheduler (sScheduler) decides what action(s) to take during
1589 // this invocation of the CC runner.
1591 // This may be zero, one, or multiple actions. (Zero is when CC is blocked by
1592 // incremental GC, or when the scheduler determined that a CC is no longer
1593 // needed.) Loop until the scheduler finishes this invocation by returning
1594 // `Yield` in step.mYield.
1595 CCRunnerStep step;
1596 do {
1597 step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(),
1598 nsCycleCollector_suspectedCount());
1599 switch (step.mAction) {
1600 case CCRunnerAction::None:
1601 break;
1603 case CCRunnerAction::MinorGC:
1604 JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(),
1605 step.mParam.mReason);
1606 sScheduler.NoteMinorGCEnd();
1607 break;
1609 case CCRunnerAction::ForgetSkippable:
1610 // 'Forget skippable' only, then end this invocation.
1611 FireForgetSkippable(bool(step.mParam.mRemoveChildless), aDeadline);
1612 break;
1614 case CCRunnerAction::CleanupContentUnbinder:
1615 // Clear content unbinder before the first actual CC slice.
1616 Element::ClearContentUnbinder();
1617 break;
1619 case CCRunnerAction::CleanupDeferred:
1620 // and if time still permits, perform deferred deletions.
1621 nsCycleCollector_doDeferredDeletion();
1622 break;
1624 case CCRunnerAction::CycleCollect:
1625 // Cycle collection slice.
1626 nsJSContext::RunCycleCollectorSlice(step.mParam.mCCReason, aDeadline);
1627 break;
1629 case CCRunnerAction::StopRunning:
1630 // End this CC, either because we have run a cycle collection slice, or
1631 // because a CC is no longer needed.
1632 sScheduler.KillCCRunner();
1633 break;
1636 if (step.mAction != CCRunnerAction::None) {
1637 didDoWork = true;
1639 } while (step.mYield == CCRunnerYield::Continue);
1641 return didDoWork;
1644 // static
1645 bool nsJSContext::HasHadCleanupSinceLastGC() {
1646 return sScheduler.IsEarlyForgetSkippable(1);
1649 // static
1650 void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
1651 mozilla::TimeStamp aDeadline) {
1652 sScheduler.RunNextCollectorTimer(aReason, aDeadline);
1655 // static
1656 void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
1657 JS::GCReason aReason) {
1658 if (!aDocShell || !XRE_IsContentProcess()) {
1659 return;
1662 BrowsingContext* bc = aDocShell->GetBrowsingContext();
1663 if (!bc) {
1664 return;
1667 BrowsingContext* root = bc->Top();
1668 if (bc == root) {
1669 // We don't want to run collectors when loading the top level page.
1670 return;
1673 nsIDocShell* rootDocShell = root->GetDocShell();
1674 if (!rootDocShell) {
1675 return;
1678 Document* rootDocument = rootDocShell->GetDocument();
1679 if (!rootDocument ||
1680 rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
1681 rootDocument->IsInBackgroundWindow()) {
1682 return;
1685 PresShell* presShell = rootDocument->GetPresShell();
1686 if (!presShell) {
1687 return;
1690 nsViewManager* vm = presShell->GetViewManager();
1691 if (!vm) {
1692 return;
1695 if (!sScheduler.IsUserActive()) {
1696 Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
1697 // Try to not delay the next RefreshDriver tick, so give a reasonable
1698 // deadline for collectors.
1699 if (next.isSome()) {
1700 sScheduler.RunNextCollectorTimer(aReason, next.value());
1705 // static
1706 void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
1707 TimeDuration aDelay) {
1708 sScheduler.PokeGC(aReason, aObj, aDelay);
1711 // static
1712 void nsJSContext::MaybePokeGC() {
1713 if (sShuttingDown) {
1714 return;
1717 JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
1718 JS::GCReason reason = JS::WantEagerMinorGC(rt);
1719 if (reason != JS::GCReason::NO_REASON) {
1720 MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION);
1721 sScheduler.PokeMinorGC(reason);
1724 // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some
1725 // benchmarks. Hopefully that will be worked out and this will check for
1726 // whether an eager major GC is needed.
1729 void nsJSContext::DoLowMemoryGC() {
1730 if (sShuttingDown) {
1731 return;
1733 nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1734 nsJSContext::ShrinkingGC);
1735 nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE);
1736 if (sScheduler.NeedsGCAfterCC()) {
1737 nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1738 nsJSContext::ShrinkingGC);
1742 // static
1743 void nsJSContext::LowMemoryGC() {
1744 RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
1745 CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE);
1746 if (!mbPromise) {
1747 // Normally when the promise is null it means that IPC failed, that probably
1748 // means that something bad happened, don't bother with the GC.
1749 return;
1751 mbPromise->Then(
1752 GetMainThreadSerialEventTarget(), __func__,
1753 [](bool aIgnored) { DoLowMemoryGC(); },
1754 [](mozilla::ipc::ResponseRejectReason r) {});
1757 // static
1758 void nsJSContext::MaybePokeCC() {
1759 sScheduler.MaybePokeCC(TimeStamp::NowLoRes(),
1760 nsCycleCollector_suspectedCount());
1763 static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
1764 const JS::GCDescription& aDesc) {
1765 NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
1767 static TimeStamp sCurrentGCStartTime;
1769 switch (aProgress) {
1770 case JS::GC_CYCLE_BEGIN: {
1771 // Prevent cycle collections and shrinking during incremental GC.
1772 sScheduler.NoteGCBegin(aDesc.reason_);
1773 sCurrentGCStartTime = TimeStamp::Now();
1774 break;
1777 case JS::GC_CYCLE_END: {
1778 TimeDuration delta = GetCollectionTimeDelta();
1780 if (StaticPrefs::javascript_options_mem_log()) {
1781 nsString gcstats;
1782 gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
1783 nsAutoString prefix;
1784 nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
1785 delta.ToSeconds(),
1786 ProcessNameForCollectorLog(), getpid());
1787 nsString msg = prefix + gcstats;
1788 nsCOMPtr<nsIConsoleService> cs =
1789 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1790 if (cs) {
1791 cs->LogStringMessage(msg.get());
1795 sScheduler.NoteGCEnd();
1797 // May need to kill the GC runner
1798 sScheduler.KillGCRunner();
1800 nsJSContext::MaybePokeCC();
1802 #if defined(MOZ_MEMORY)
1803 bool freeDirty = false;
1804 #endif
1805 if (aDesc.isZone_) {
1806 sScheduler.PokeFullGC();
1807 } else {
1808 #if defined(MOZ_MEMORY)
1809 freeDirty = true;
1810 #endif
1811 sScheduler.SetNeedsFullGC(false);
1812 sScheduler.KillFullGCTimer();
1815 if (sScheduler.IsCCNeeded(TimeStamp::Now(),
1816 nsCycleCollector_suspectedCount()) !=
1817 CCReason::NO_REASON) {
1818 #if defined(MOZ_MEMORY)
1819 // We're likely to free the dirty pages after CC.
1820 freeDirty = false;
1821 #endif
1822 nsCycleCollector_dispatchDeferredDeletion();
1825 Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
1826 TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
1828 #if defined(MOZ_MEMORY)
1829 if (freeDirty &&
1830 StaticPrefs::
1831 dom_memory_foreground_content_processes_have_larger_page_cache()) {
1832 jemalloc_free_dirty_pages();
1834 #endif
1835 break;
1838 case JS::GC_SLICE_BEGIN:
1839 break;
1841 case JS::GC_SLICE_END:
1842 sScheduler.NoteGCSliceEnd(aDesc.lastSliceStart(aCx),
1843 aDesc.lastSliceEnd(aCx));
1845 if (sShuttingDown) {
1846 sScheduler.KillGCRunner();
1847 } else {
1848 // If incremental GC wasn't triggered by GCTimerFired, we may not have a
1849 // runner to ensure all the slices are handled. So, create the runner
1850 // here.
1851 sScheduler.EnsureGCRunner(0);
1854 if (sScheduler.IsCCNeeded(TimeStamp::Now(),
1855 nsCycleCollector_suspectedCount()) !=
1856 CCReason::NO_REASON) {
1857 nsCycleCollector_dispatchDeferredDeletion();
1860 if (StaticPrefs::javascript_options_mem_log()) {
1861 nsString gcstats;
1862 gcstats.Adopt(aDesc.formatSliceMessage(aCx));
1863 nsAutoString prefix;
1864 nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
1865 ProcessNameForCollectorLog(), getpid());
1866 nsString msg = prefix + gcstats;
1867 nsCOMPtr<nsIConsoleService> cs =
1868 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1869 if (cs) {
1870 cs->LogStringMessage(msg.get());
1874 break;
1876 default:
1877 MOZ_CRASH("Unexpected GCProgress value");
1880 if (sPrevGCSliceCallback) {
1881 (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
1885 void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
1886 mWindowProxy = aWindowProxy;
1889 JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
1891 void nsJSContext::LikelyShortLivingObjectCreated() {
1892 ++sScheduler.mLikelyShortLivingObjectsNeedingGC;
1895 void mozilla::dom::StartupJSEnvironment() {
1896 // initialize all our statics, so that we can restart XPCOM
1897 sIsInitialized = false;
1898 sShuttingDown = false;
1899 new (&sScheduler) CCGCScheduler(); // Reset the scheduler state.
1900 sCCStats.Init();
1903 static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
1904 AutoJSAPI jsapi;
1905 jsapi.Init();
1906 JS_SetGCParameter(jsapi.cx(), aParam, aValue);
1909 static void ResetGCParameter(JSGCParamKey aParam) {
1910 AutoJSAPI jsapi;
1911 jsapi.Init();
1912 JS_ResetGCParameter(jsapi.cx(), aParam);
1915 static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
1916 void* aClosure) {
1917 int32_t prefMB = Preferences::GetInt(aPrefName, -1);
1918 // handle overflow and negative pref values
1919 CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
1920 if (prefB.isValid() && prefB.value() >= 0) {
1921 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1922 } else {
1923 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1927 static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
1928 void* aClosure) {
1929 int32_t prefKB = Preferences::GetInt(aPrefName, -1);
1930 // handle overflow and negative pref values
1931 CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
1932 if (prefB.isValid() && prefB.value() >= 0) {
1933 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1934 } else {
1935 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1939 static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
1940 void* aClosure) {
1941 int32_t pref = Preferences::GetInt(aPrefName, -1);
1942 // handle overflow and negative pref values
1943 if (pref >= 0 && pref < 10000) {
1944 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1945 } else {
1946 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1950 static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
1951 void* aClosure) {
1952 bool pref = Preferences::GetBool(aPrefName);
1953 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1956 static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
1957 void* aClosure) {
1958 int32_t pref = Preferences::GetInt(aPrefName, -1);
1959 // handle overflow and negative pref values
1960 if (pref > 0 && pref < 100000) {
1961 sScheduler.SetActiveIntersliceGCBudget(
1962 TimeDuration::FromMilliseconds(pref));
1963 SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
1964 } else {
1965 ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
1969 static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
1970 void* aClosure) {
1971 bool pref = Preferences::GetBool(aPrefName);
1972 sIncrementalCC = pref;
1975 class JSDispatchableRunnable final : public Runnable {
1976 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
1978 public:
1979 explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
1980 : mozilla::Runnable("JSDispatchableRunnable"),
1981 mDispatchable(aDispatchable) {
1982 MOZ_ASSERT(mDispatchable);
1985 protected:
1986 NS_IMETHOD Run() override {
1987 MOZ_ASSERT(NS_IsMainThread());
1989 AutoJSAPI jsapi;
1990 jsapi.Init();
1992 JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
1993 sShuttingDown ? JS::Dispatchable::ShuttingDown
1994 : JS::Dispatchable::NotShuttingDown;
1996 mDispatchable->run(jsapi.cx(), maybeShuttingDown);
1997 mDispatchable = nullptr; // mDispatchable may delete itself
1999 return NS_OK;
2002 private:
2003 JS::Dispatchable* mDispatchable;
2006 static bool DispatchToEventLoop(void* closure,
2007 JS::Dispatchable* aDispatchable) {
2008 MOZ_ASSERT(!closure);
2010 // This callback may execute either on the main thread or a random JS-internal
2011 // helper thread. This callback can be called during shutdown so we cannot
2012 // simply NS_DispatchToMainThread. Failure during shutdown is expected and
2013 // properly handled by the JS engine.
2015 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
2016 if (!mainTarget) {
2017 return false;
2020 RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
2021 MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
2022 return true;
2025 static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
2026 JS::MimeType aMimeType,
2027 JS::StreamConsumer* aConsumer) {
2028 return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
2029 nullptr);
2032 static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason,
2033 int64_t aMillis) {
2034 return sScheduler.CreateGCSliceBudget(
2035 mozilla::TimeDuration::FromMilliseconds(aMillis), false, false);
2038 void nsJSContext::EnsureStatics() {
2039 if (sIsInitialized) {
2040 if (!nsContentUtils::XPConnect()) {
2041 MOZ_CRASH();
2043 return;
2046 // Let's make sure that our main thread is the same as the xpcom main thread.
2047 MOZ_ASSERT(NS_IsMainThread());
2049 AutoJSAPI jsapi;
2050 jsapi.Init();
2052 sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
2054 JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget);
2056 JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
2057 JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
2058 FetchUtil::ReportJSStreamError);
2060 // Set these global xpconnect options...
2061 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
2062 "javascript.options.mem.max",
2063 (void*)JSGC_MAX_BYTES);
2064 Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
2065 "javascript.options.mem.nursery.min_kb",
2066 (void*)JSGC_MIN_NURSERY_BYTES);
2067 Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
2068 "javascript.options.mem.nursery.max_kb",
2069 (void*)JSGC_MAX_NURSERY_BYTES);
2071 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
2072 "javascript.options.mem.gc_per_zone",
2073 (void*)JSGC_PER_ZONE_GC_ENABLED);
2075 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
2076 "javascript.options.mem.gc_incremental",
2077 (void*)JSGC_INCREMENTAL_GC_ENABLED);
2079 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
2080 "javascript.options.mem.gc_compacting",
2081 (void*)JSGC_COMPACTING_ENABLED);
2083 Preferences::RegisterCallbackAndCall(
2084 SetMemoryPrefChangedCallbackBool,
2085 "javascript.options.mem.gc_parallel_marking",
2086 (void*)JSGC_PARALLEL_MARKING_ENABLED);
2088 Preferences::RegisterCallbackAndCall(
2089 SetMemoryPrefChangedCallbackInt,
2090 "javascript.options.mem.gc_parallel_marking_threshold_kb",
2091 (void*)JSGC_PARALLEL_MARKING_THRESHOLD_KB);
2093 Preferences::RegisterCallbackAndCall(
2094 SetMemoryGCSliceTimePrefChangedCallback,
2095 "javascript.options.mem.gc_incremental_slice_ms");
2097 Preferences::RegisterCallbackAndCall(
2098 SetMemoryPrefChangedCallbackBool,
2099 "javascript.options.mem.incremental_weakmap",
2100 (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
2102 Preferences::RegisterCallbackAndCall(
2103 SetMemoryPrefChangedCallbackInt,
2104 "javascript.options.mem.gc_high_frequency_time_limit_ms",
2105 (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
2107 Preferences::RegisterCallbackAndCall(
2108 SetMemoryPrefChangedCallbackInt,
2109 "javascript.options.mem.gc_low_frequency_heap_growth",
2110 (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
2112 Preferences::RegisterCallbackAndCall(
2113 SetMemoryPrefChangedCallbackInt,
2114 "javascript.options.mem.gc_high_frequency_large_heap_growth",
2115 (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
2117 Preferences::RegisterCallbackAndCall(
2118 SetMemoryPrefChangedCallbackInt,
2119 "javascript.options.mem.gc_high_frequency_small_heap_growth",
2120 (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
2122 Preferences::RegisterCallbackAndCall(
2123 SetMemoryPrefChangedCallbackBool,
2124 "javascript.options.mem.gc_balanced_heap_limits",
2125 (void*)JSGC_BALANCED_HEAP_LIMITS_ENABLED);
2127 Preferences::RegisterCallbackAndCall(
2128 SetMemoryPrefChangedCallbackInt,
2129 "javascript.options.mem.gc_heap_growth_factor",
2130 (void*)JSGC_HEAP_GROWTH_FACTOR);
2132 Preferences::RegisterCallbackAndCall(
2133 SetMemoryPrefChangedCallbackInt,
2134 "javascript.options.mem.gc_small_heap_size_max_mb",
2135 (void*)JSGC_SMALL_HEAP_SIZE_MAX);
2137 Preferences::RegisterCallbackAndCall(
2138 SetMemoryPrefChangedCallbackInt,
2139 "javascript.options.mem.gc_large_heap_size_min_mb",
2140 (void*)JSGC_LARGE_HEAP_SIZE_MIN);
2142 Preferences::RegisterCallbackAndCall(
2143 SetMemoryPrefChangedCallbackInt,
2144 "javascript.options.mem.gc_allocation_threshold_mb",
2145 (void*)JSGC_ALLOCATION_THRESHOLD);
2147 Preferences::RegisterCallbackAndCall(
2148 SetMemoryPrefChangedCallbackInt,
2149 "javascript.options.mem.gc_malloc_threshold_base_mb",
2150 (void*)JSGC_MALLOC_THRESHOLD_BASE);
2152 Preferences::RegisterCallbackAndCall(
2153 SetMemoryPrefChangedCallbackInt,
2154 "javascript.options.mem.gc_small_heap_incremental_limit",
2155 (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
2156 Preferences::RegisterCallbackAndCall(
2157 SetMemoryPrefChangedCallbackInt,
2158 "javascript.options.mem.gc_large_heap_incremental_limit",
2159 (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
2161 Preferences::RegisterCallbackAndCall(
2162 SetMemoryPrefChangedCallbackInt,
2163 "javascript.options.mem.gc_urgent_threshold_mb",
2164 (void*)JSGC_URGENT_THRESHOLD_MB);
2166 Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
2167 "dom.cycle_collector.incremental");
2169 Preferences::RegisterCallbackAndCall(
2170 SetMemoryPrefChangedCallbackInt,
2171 "javascript.options.mem.gc_min_empty_chunk_count",
2172 (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
2174 Preferences::RegisterCallbackAndCall(
2175 SetMemoryPrefChangedCallbackInt,
2176 "javascript.options.mem.gc_max_empty_chunk_count",
2177 (void*)JSGC_MAX_EMPTY_CHUNK_COUNT);
2179 Preferences::RegisterCallbackAndCall(
2180 SetMemoryPrefChangedCallbackInt,
2181 "javascript.options.mem.gc_helper_thread_ratio",
2182 (void*)JSGC_HELPER_THREAD_RATIO);
2184 Preferences::RegisterCallbackAndCall(
2185 SetMemoryPrefChangedCallbackInt,
2186 "javascript.options.mem.gc_max_helper_threads",
2187 (void*)JSGC_MAX_HELPER_THREADS);
2189 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2190 if (!obs) {
2191 MOZ_CRASH();
2194 nsIObserver* observer = new nsJSEnvironmentObserver();
2195 obs->AddObserver(observer, "memory-pressure", false);
2196 obs->AddObserver(observer, "user-interaction-inactive", false);
2197 obs->AddObserver(observer, "user-interaction-active", false);
2198 obs->AddObserver(observer, "quit-application", false);
2199 obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
2200 obs->AddObserver(observer, "content-child-will-shutdown", false);
2202 sIsInitialized = true;
2205 void mozilla::dom::ShutdownJSEnvironment() {
2206 sShuttingDown = true;
2207 sScheduler.Shutdown();
2210 AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
2211 : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
2213 void AsyncErrorReporter::SerializeStack(JSContext* aCx,
2214 JS::Handle<JSObject*> aStack) {
2215 mStackHolder = MakeUnique<SerializedStackHolder>();
2216 mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
2219 void AsyncErrorReporter::SetException(JSContext* aCx,
2220 JS::Handle<JS::Value> aException) {
2221 MOZ_ASSERT(NS_IsMainThread());
2222 mException.init(aCx, aException);
2223 mHasException = true;
2226 NS_IMETHODIMP AsyncErrorReporter::Run() {
2227 AutoJSAPI jsapi;
2228 // We're only using this context to deserialize a stack to report to the
2229 // console, so the scope we use doesn't matter. Stack frame filtering happens
2230 // based on the principal encoded into the frame and the caller compartment,
2231 // not the compartment of the frame object, and the console reporting code
2232 // will not be using our context, and therefore will not care what compartment
2233 // it has entered.
2234 DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
2235 MOZ_ASSERT(ok, "Problem with system global?");
2236 JSContext* cx = jsapi.cx();
2237 JS::Rooted<JSObject*> stack(cx);
2238 JS::Rooted<JSObject*> stackGlobal(cx);
2239 if (mStackHolder) {
2240 stack = mStackHolder->ReadStack(cx);
2241 if (stack) {
2242 stackGlobal = JS::CurrentGlobalOrNull(cx);
2246 JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
2247 if (mHasException) {
2248 MOZ_ASSERT(NS_IsMainThread());
2249 exception = Some(mException);
2250 // Remove our reference to the exception.
2251 mException.setUndefined();
2252 mHasException = false;
2255 mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
2256 return NS_OK;
2259 // A fast-array class for JS. This class supports both nsIJSScriptArray and
2260 // nsIArray. If it is JS itself providing and consuming this class, all work
2261 // can be done via nsIJSScriptArray, and avoid the conversion of elements
2262 // to/from nsISupports.
2263 // When consumed by non-JS (eg, another script language), conversion is done
2264 // on-the-fly.
2265 class nsJSArgArray final : public nsIJSArgArray {
2266 public:
2267 nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
2268 nsresult* prv);
2270 // nsISupports
2271 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
2272 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
2273 nsIJSArgArray)
2275 // nsIArray
2276 NS_DECL_NSIARRAY
2278 // nsIJSArgArray
2279 nsresult GetArgs(uint32_t* argc, void** argv) override;
2281 void ReleaseJSObjects();
2283 protected:
2284 ~nsJSArgArray();
2285 JSContext* mContext;
2286 JS::Heap<JS::Value>* mArgv;
2287 uint32_t mArgc;
2290 nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
2291 const JS::Value* argv, nsresult* prv)
2292 : mContext(aContext), mArgv(nullptr), mArgc(argc) {
2293 // copy the array - we don't know its lifetime, and ours is tied to xpcom
2294 // refcounting.
2295 if (argc) {
2296 mArgv = new (fallible) JS::Heap<JS::Value>[argc];
2297 if (!mArgv) {
2298 *prv = NS_ERROR_OUT_OF_MEMORY;
2299 return;
2303 // Callers are allowed to pass in a null argv even for argc > 0. They can
2304 // then use GetArgs to initialize the values.
2305 if (argv) {
2306 for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
2309 if (argc > 0) {
2310 mozilla::HoldJSObjects(this);
2313 *prv = NS_OK;
2316 nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
2318 void nsJSArgArray::ReleaseJSObjects() {
2319 delete[] mArgv;
2321 if (mArgc > 0) {
2322 mArgc = 0;
2323 mozilla::DropJSObjects(this);
2327 // QueryInterface implementation for nsJSArgArray
2328 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray)
2330 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
2331 tmp->ReleaseJSObjects();
2332 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2333 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
2334 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2336 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
2337 if (tmp->mArgv) {
2338 for (uint32_t i = 0; i < tmp->mArgc; ++i) {
2339 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
2342 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2344 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
2345 NS_INTERFACE_MAP_ENTRY(nsIArray)
2346 NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
2347 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
2348 NS_INTERFACE_MAP_END
2350 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
2351 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
2353 nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
2354 *argv = (void*)mArgv;
2355 *argc = mArgc;
2356 return NS_OK;
2359 // nsIArray impl
2360 NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
2361 *aLength = mArgc;
2362 return NS_OK;
2365 NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
2366 void** result) {
2367 *result = nullptr;
2368 if (index >= mArgc) return NS_ERROR_INVALID_ARG;
2370 if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
2371 uuid.Equals(NS_GET_IID(nsISupports))) {
2372 // Have to copy a Heap into a Rooted to work with it.
2373 JS::Rooted<JS::Value> val(mContext, mArgv[index]);
2374 return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
2375 (nsIVariant**)result);
2377 NS_WARNING("nsJSArgArray only handles nsIVariant");
2378 return NS_ERROR_NO_INTERFACE;
2381 NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
2382 uint32_t* _retval) {
2383 return NS_ERROR_NOT_IMPLEMENTED;
2386 NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
2387 uint8_t aArgc,
2388 nsISimpleEnumerator** aResult) {
2389 return NS_ERROR_NOT_IMPLEMENTED;
2392 NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
2393 nsISimpleEnumerator** _retval) {
2394 return NS_ERROR_NOT_IMPLEMENTED;
2397 // The factory function
2398 nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
2399 const JS::Value* argv, nsIJSArgArray** aArray) {
2400 nsresult rv;
2401 nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
2402 if (NS_FAILED(rv)) {
2403 return rv;
2405 ret.forget(aArray);
2406 return NS_OK;