Backed out changeset b462e7b742d8 (bug 1908261) for causing multiple reftest failures...
[gecko.git] / dom / base / nsJSEnvironment.cpp
blob11130fd145e1108e29d3d7deb274d4acfa0cc007
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/CycleCollectorStats.h"
58 #include "mozilla/MainThreadIdlePeriod.h"
59 #include "mozilla/PresShell.h"
60 #include "mozilla/SchedulerGroup.h"
61 #include "mozilla/StaticPrefs_dom.h"
62 #include "mozilla/StaticPrefs_javascript.h"
63 #include "mozilla/StaticPtr.h"
64 #include "mozilla/dom/BrowsingContext.h"
65 #include "mozilla/dom/DOMException.h"
66 #include "mozilla/dom/DOMExceptionBinding.h"
67 #include "mozilla/dom/Element.h"
68 #include "mozilla/dom/ErrorEvent.h"
69 #include "mozilla/dom/FetchUtil.h"
70 #include "mozilla/dom/RootedDictionary.h"
71 #include "mozilla/dom/ScriptSettings.h"
72 #include "mozilla/dom/SerializedStackHolder.h"
73 #include "mozilla/CycleCollectedJSRuntime.h"
74 #include "nsRefreshDriver.h"
75 #include "nsJSPrincipals.h"
76 #include "AccessCheck.h"
77 #include "mozilla/Logging.h"
78 #include "prthread.h"
80 #include "mozilla/Preferences.h"
81 #include "mozilla/Telemetry.h"
82 #include "mozilla/dom/BindingUtils.h"
83 #include "mozilla/Attributes.h"
84 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
85 #include "mozilla/ContentEvents.h"
86 #include "mozilla/CycleCollectedJSContext.h"
87 #include "nsCycleCollectionNoteRootCallback.h"
88 #include "nsViewManager.h"
89 #include "mozilla/EventStateManager.h"
90 #include "mozilla/ProfilerLabels.h"
91 #include "mozilla/ProfilerMarkers.h"
92 #if defined(MOZ_MEMORY)
93 # include "mozmemory.h"
94 #endif
96 using namespace mozilla;
97 using namespace mozilla::dom;
99 // Thank you Microsoft!
100 #ifdef CompareString
101 # undef CompareString
102 #endif
104 static JS::GCSliceCallback sPrevGCSliceCallback;
106 static bool sIncrementalCC = false;
108 static bool sIsInitialized;
109 static bool sShuttingDown;
111 static CCGCScheduler* sScheduler = nullptr;
112 static std::aligned_storage_t<sizeof(*sScheduler)> sSchedulerStorage;
114 // Cache a pointer to the main thread's statistics struct.
115 static CycleCollectorStats* sCCStats = nullptr;
117 static const char* ProcessNameForCollectorLog() {
118 return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
119 : "content";
122 namespace xpc {
124 // This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
125 // Exceptions, and arbitrary values that were associated with a stack by the
126 // JS engine when they were thrown, as specified by exceptionStack.
128 // Note that the returned stackObj and stackGlobal are _not_ wrapped into the
129 // compartment of exceptionValue.
130 void FindExceptionStackForConsoleReport(
131 nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue,
132 JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj,
133 JS::MutableHandle<JSObject*> stackGlobal) {
134 stackObj.set(nullptr);
135 stackGlobal.set(nullptr);
137 if (!exceptionValue.isObject()) {
138 // Use the stack provided by the JS engine, if available. This will not be
139 // a wrapper.
140 if (exceptionStack) {
141 stackObj.set(exceptionStack);
142 stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
144 return;
147 if (win && win->AsGlobal()->IsDying()) {
148 // Pretend like we have no stack, so we don't end up keeping the global
149 // alive via the stack.
150 return;
153 JS::RootingContext* rcx = RootingCx();
154 JS::Rooted<JSObject*> exceptionObject(rcx, &exceptionValue.toObject());
155 if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
156 // At this point we know exceptionObject is a possibly-wrapped
157 // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
158 // but excStack must be same-compartment with the unwrapped ErrorObject.
159 // Return the ErrorObject's global as stackGlobal. This matches what we do
160 // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
161 // are same-compartment.
162 JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
163 stackObj.set(excStack);
164 stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
165 return;
168 // It is not a JS Exception, try DOM Exception.
169 RefPtr<Exception> exception;
170 UNWRAP_OBJECT(DOMException, exceptionObject, exception);
171 if (!exception) {
172 // Not a DOM Exception, try XPC Exception.
173 UNWRAP_OBJECT(Exception, exceptionObject, exception);
174 if (!exception) {
175 // As above, use the stack provided by the JS engine, if available.
176 if (exceptionStack) {
177 stackObj.set(exceptionStack);
178 stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
180 return;
184 nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
185 if (!stack) {
186 return;
188 JS::Rooted<JS::Value> value(rcx);
189 stack->GetNativeSavedFrame(&value);
190 if (value.isObject()) {
191 stackObj.set(&value.toObject());
192 MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
193 stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
194 return;
198 } /* namespace xpc */
200 static TimeDuration GetCollectionTimeDelta() {
201 static TimeStamp sFirstCollectionTime;
202 TimeStamp now = TimeStamp::Now();
203 if (sFirstCollectionTime) {
204 return now - sFirstCollectionTime;
206 sFirstCollectionTime = now;
207 return TimeDuration();
210 class nsJSEnvironmentObserver final : public nsIObserver {
211 ~nsJSEnvironmentObserver() = default;
213 public:
214 NS_DECL_ISUPPORTS
215 NS_DECL_NSIOBSERVER
218 NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
220 NS_IMETHODIMP
221 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
222 const char16_t* aData) {
223 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
224 if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
225 if (sShuttingDown) {
226 // Don't GC/CC if we're already shutting down.
227 return NS_OK;
229 nsDependentString data(aData);
230 if (data.EqualsLiteral("low-memory-ongoing")) {
231 // Don't GC/CC if we are in an ongoing low-memory state since its very
232 // slow and it likely won't help us anyway.
233 return NS_OK;
235 if (data.EqualsLiteral("heap-minimize")) {
236 // heap-minimize notifiers expect this to run synchronously
237 nsJSContext::DoLowMemoryGC();
238 return NS_OK;
240 if (data.EqualsLiteral("low-memory")) {
241 nsJSContext::SetLowMemoryState(true);
243 // Asynchronously GC.
244 nsJSContext::LowMemoryGC();
246 } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
247 nsJSContext::SetLowMemoryState(false);
248 } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
249 sScheduler->UserIsInactive();
250 } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
251 sScheduler->UserIsActive();
252 } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
253 !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
254 !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
255 sShuttingDown = true;
256 sScheduler->Shutdown();
259 return NS_OK;
262 /****************************************************************
263 ************************** AutoFree ****************************
264 ****************************************************************/
266 class AutoFree {
267 public:
268 explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
269 ~AutoFree() {
270 if (mPtr) free(mPtr);
272 void Invalidate() { mPtr = nullptr; }
274 private:
275 void* mPtr;
278 // A utility function for script languages to call. Although it looks small,
279 // the use of nsIDocShell and nsPresContext triggers a huge number of
280 // dependencies that most languages would not otherwise need.
281 // XXXmarkh - This function is mis-placed!
282 bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
283 const ErrorEventInit& aErrorEventInit,
284 nsEventStatus* aStatus) {
285 bool called = false;
286 nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
287 nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
288 if (docShell) {
289 RefPtr<nsPresContext> presContext = docShell->GetPresContext();
291 static int32_t errorDepth; // Recursion prevention
292 ++errorDepth;
294 if (errorDepth < 2) {
295 // Dispatch() must be synchronous for the recursion block
296 // (errorDepth) to work.
297 RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
298 nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
299 event->SetTrusted(true);
301 // MOZ_KnownLive due to bug 1506441
302 EventDispatcher::DispatchDOMEvent(
303 MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
304 presContext, aStatus);
305 called = true;
307 --errorDepth;
309 return called;
312 class ScriptErrorEvent : public Runnable {
313 public:
314 ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
315 xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
316 JS::Handle<JSObject*> aErrorStack)
317 : mozilla::Runnable("ScriptErrorEvent"),
318 mWindow(aWindow),
319 mReport(aReport),
320 mError(aRootingCx, aError),
321 mErrorStack(aRootingCx, aErrorStack) {}
323 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
324 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
325 nsEventStatus status = nsEventStatus_eIgnore;
326 nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
327 MOZ_ASSERT(win);
328 MOZ_ASSERT(NS_IsMainThread());
329 // First, notify the DOM that we have a script error, but only if
330 // our window is still the current inner.
331 JS::RootingContext* rootingCx = RootingCx();
332 if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
333 !sHandlingScriptError) {
334 AutoRestore<bool> recursionGuard(sHandlingScriptError);
335 sHandlingScriptError = true;
337 RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
339 RootedDictionary<ErrorEventInit> init(rootingCx);
340 init.mCancelable = true;
341 init.mFilename = mReport->mFileName;
342 init.mBubbles = true;
344 constexpr auto xoriginMsg = u"Script error."_ns;
345 if (!mReport->mIsMuted) {
346 init.mMessage = mReport->mErrorMsg;
347 init.mLineno = mReport->mLineNumber;
348 init.mColno = mReport->mColumn;
349 init.mError = mError;
350 } else {
351 NS_WARNING("Not same origin error!");
352 init.mMessage = xoriginMsg;
353 init.mLineno = 0;
356 RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
357 nsGlobalWindowInner::Cast(win), u"error"_ns, init);
358 event->SetTrusted(true);
360 // MOZ_KnownLive due to bug 1506441
361 EventDispatcher::DispatchDOMEvent(
362 MOZ_KnownLive(nsGlobalWindowInner::Cast(win)), nullptr, event,
363 presContext, &status);
366 if (status != nsEventStatus_eConsumeNoDefault) {
367 JS::Rooted<JSObject*> stack(rootingCx);
368 JS::Rooted<JSObject*> stackGlobal(rootingCx);
369 xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
370 &stackGlobal);
371 JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
372 nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
373 mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
376 return NS_OK;
379 private:
380 nsCOMPtr<nsPIDOMWindowInner> mWindow;
381 RefPtr<xpc::ErrorReport> mReport;
382 JS::PersistentRooted<JS::Value> mError;
383 JS::PersistentRooted<JSObject*> mErrorStack;
385 static bool sHandlingScriptError;
388 bool ScriptErrorEvent::sHandlingScriptError = false;
390 // This temporarily lives here to avoid code churn. It will go away entirely
391 // soon.
392 namespace xpc {
394 void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
395 JS::RootingContext* rootingCx,
396 xpc::ErrorReport* xpcReport,
397 JS::Handle<JS::Value> exception,
398 JS::Handle<JSObject*> exceptionStack) {
399 nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
400 win, rootingCx, xpcReport, exception, exceptionStack));
403 } /* namespace xpc */
405 #ifdef DEBUG
406 // A couple of useful functions to call when you're debugging.
407 nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
408 return xpc::WindowOrNull(obj);
411 template <typename T>
412 void PrintWinURI(T* win) {
413 if (!win) {
414 printf("No window passed in.\n");
415 return;
418 nsCOMPtr<Document> doc = win->GetExtantDoc();
419 if (!doc) {
420 printf("No document in the window.\n");
421 return;
424 nsIURI* uri = doc->GetDocumentURI();
425 if (!uri) {
426 printf("Document doesn't have a URI.\n");
427 return;
430 printf("%s\n", uri->GetSpecOrDefault().get());
433 void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
435 void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
437 template <typename T>
438 void PrintWinCodebase(T* win) {
439 if (!win) {
440 printf("No window passed in.\n");
441 return;
444 nsIPrincipal* prin = win->GetPrincipal();
445 if (!prin) {
446 printf("Window doesn't have principals.\n");
447 return;
449 if (prin->IsSystemPrincipal()) {
450 printf("No URI, it's the system principal.\n");
451 return;
453 nsCString spec;
454 prin->GetAsciiSpec(spec);
455 printf("%s\n", spec.get());
458 void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
459 return PrintWinCodebase(aWin);
462 void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
463 return PrintWinCodebase(aWin);
466 void DumpString(const nsAString& str) {
467 printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
469 #endif
471 nsJSContext::nsJSContext(bool aGCOnDestruction,
472 nsIScriptGlobalObject* aGlobalObject)
473 : mWindowProxy(nullptr),
474 mGCOnDestruction(aGCOnDestruction),
475 mGlobalObjectRef(aGlobalObject) {
476 EnsureStatics();
478 mProcessingScriptTag = false;
479 HoldJSObjects(this);
482 nsJSContext::~nsJSContext() {
483 mGlobalObjectRef = nullptr;
485 Destroy();
488 void nsJSContext::Destroy() {
489 if (mGCOnDestruction) {
490 sScheduler->PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
493 DropJSObjects(this);
496 // QueryInterface implementation for nsJSContext
497 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
499 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
500 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
501 NS_IMPL_CYCLE_COLLECTION_TRACE_END
503 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
504 tmp->mGCOnDestruction = false;
505 tmp->mWindowProxy = nullptr;
506 tmp->Destroy();
507 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
508 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
513 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
514 NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
515 NS_INTERFACE_MAP_ENTRY(nsISupports)
516 NS_INTERFACE_MAP_END
518 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
519 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
521 #ifdef DEBUG
522 bool AtomIsEventHandlerName(nsAtom* aName) {
523 const char16_t* name = aName->GetUTF16String();
525 const char16_t* cp;
526 char16_t c;
527 for (cp = name; *cp != '\0'; ++cp) {
528 c = *cp;
529 if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
532 return true;
534 #endif
536 nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
537 // Note: this could probably be simplified somewhat more; see bug 974327
538 // comments 1 and 3.
539 if (!mWindowProxy) {
540 return nullptr;
543 MOZ_ASSERT(mGlobalObjectRef);
544 return mGlobalObjectRef;
547 nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
548 const char* aPropName, nsISupports* aArgs) {
549 AutoJSAPI jsapi;
550 if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
551 return NS_ERROR_FAILURE;
553 JSContext* cx = jsapi.cx();
555 JS::RootedVector<JS::Value> args(cx);
557 JS::Rooted<JSObject*> global(cx, GetWindowProxy());
558 nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
559 NS_ENSURE_SUCCESS(rv, rv);
561 // got the arguments, now attach them.
563 for (uint32_t i = 0; i < args.length(); ++i) {
564 if (!JS_WrapValue(cx, args[i])) {
565 return NS_ERROR_FAILURE;
569 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
570 if (!array) {
571 return NS_ERROR_FAILURE;
574 return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
575 : NS_ERROR_FAILURE;
578 nsresult nsJSContext::ConvertSupportsTojsvals(
579 JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
580 JS::MutableHandleVector<JS::Value> aArgsOut) {
581 nsresult rv = NS_OK;
583 // If the array implements nsIJSArgArray, copy the contents and return.
584 nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
585 if (fastArray) {
586 uint32_t argc;
587 JS::Value* argv;
588 rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
589 if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
590 rv = NS_ERROR_OUT_OF_MEMORY;
592 return rv;
595 // Take the slower path converting each item.
596 // Handle only nsIArray and nsIVariant. nsIArray is only needed for
597 // SetProperty('arguments', ...);
599 nsIXPConnect* xpc = nsContentUtils::XPConnect();
600 NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
602 if (!aArgs) return NS_OK;
603 uint32_t argCount;
604 // This general purpose function may need to convert an arg array
605 // (window.arguments, event-handler args) and a generic property.
606 nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
608 if (argsArray) {
609 rv = argsArray->GetLength(&argCount);
610 NS_ENSURE_SUCCESS(rv, rv);
611 if (argCount == 0) return NS_OK;
612 } else {
613 argCount = 1; // the nsISupports which is not an array
616 // Use the caller's auto guards to release and unroot.
617 if (!aArgsOut.resize(argCount)) {
618 return NS_ERROR_OUT_OF_MEMORY;
621 if (argsArray) {
622 for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
623 nsCOMPtr<nsISupports> arg;
624 JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
625 argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
626 getter_AddRefs(arg));
627 if (!arg) {
628 thisVal.setNull();
629 continue;
631 nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
632 if (variant != nullptr) {
633 rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
634 } else {
635 // And finally, support the nsISupportsPrimitives supplied
636 // by the AppShell. It generally will pass only strings, but
637 // as we have code for handling all, we may as well use it.
638 rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
639 if (rv == NS_ERROR_NO_INTERFACE) {
640 // something else - probably an event object or similar -
641 // just wrap it.
642 #ifdef DEBUG
643 // but first, check its not another nsISupportsPrimitive, as
644 // these are now deprecated for use with script contexts.
645 nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
646 NS_ASSERTION(prim == nullptr,
647 "Don't pass nsISupportsPrimitives - use nsIVariant!");
648 #endif
649 JSAutoRealm ar(aCx, aScope);
650 rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
654 } else {
655 nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
656 if (variant) {
657 rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
658 } else {
659 NS_ERROR("Not an array, not an interface?");
660 rv = NS_ERROR_UNEXPECTED;
663 return rv;
666 // This really should go into xpconnect somewhere...
667 nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
668 nsISupports* aArg,
669 JS::Value* aArgv) {
670 MOZ_ASSERT(aArg, "Empty arg");
672 nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
673 if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
675 uint16_t type;
676 argPrimitive->GetType(&type);
678 switch (type) {
679 case nsISupportsPrimitive::TYPE_CSTRING: {
680 nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
681 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
683 nsAutoCString data;
685 p->GetData(data);
687 JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
688 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
690 aArgv->setString(str);
692 break;
694 case nsISupportsPrimitive::TYPE_STRING: {
695 nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
696 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
698 nsAutoString data;
700 p->GetData(data);
702 // cast is probably safe since wchar_t and char16_t are expected
703 // to be equivalent; both unsigned 16-bit entities
704 JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
705 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
707 aArgv->setString(str);
708 break;
710 case nsISupportsPrimitive::TYPE_PRBOOL: {
711 nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
712 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
714 bool data;
716 p->GetData(&data);
718 aArgv->setBoolean(data);
720 break;
722 case nsISupportsPrimitive::TYPE_PRUINT8: {
723 nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
724 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
726 uint8_t data;
728 p->GetData(&data);
730 aArgv->setInt32(data);
732 break;
734 case nsISupportsPrimitive::TYPE_PRUINT16: {
735 nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
736 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
738 uint16_t data;
740 p->GetData(&data);
742 aArgv->setInt32(data);
744 break;
746 case nsISupportsPrimitive::TYPE_PRUINT32: {
747 nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
748 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
750 uint32_t data;
752 p->GetData(&data);
754 aArgv->setInt32(data);
756 break;
758 case nsISupportsPrimitive::TYPE_CHAR: {
759 nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
760 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
762 char data;
764 p->GetData(&data);
766 JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
767 NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
769 aArgv->setString(str);
771 break;
773 case nsISupportsPrimitive::TYPE_PRINT16: {
774 nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
775 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
777 int16_t data;
779 p->GetData(&data);
781 aArgv->setInt32(data);
783 break;
785 case nsISupportsPrimitive::TYPE_PRINT32: {
786 nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
787 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
789 int32_t data;
791 p->GetData(&data);
793 aArgv->setInt32(data);
795 break;
797 case nsISupportsPrimitive::TYPE_FLOAT: {
798 nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
799 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
801 float data;
803 p->GetData(&data);
805 *aArgv = ::JS_NumberValue(data);
807 break;
809 case nsISupportsPrimitive::TYPE_DOUBLE: {
810 nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
811 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
813 double data;
815 p->GetData(&data);
817 *aArgv = ::JS_NumberValue(data);
819 break;
821 case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
822 nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
823 NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
825 nsCOMPtr<nsISupports> data;
826 nsIID* iid = nullptr;
828 p->GetData(getter_AddRefs(data));
829 p->GetDataIID(&iid);
830 NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
832 AutoFree iidGuard(iid); // Free iid upon destruction.
834 JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
835 JS::Rooted<JS::Value> v(aCx);
836 JSAutoRealm ar(aCx, scope);
837 nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
838 NS_ENSURE_SUCCESS(rv, rv);
840 *aArgv = v;
842 break;
844 case nsISupportsPrimitive::TYPE_ID:
845 case nsISupportsPrimitive::TYPE_PRUINT64:
846 case nsISupportsPrimitive::TYPE_PRINT64:
847 case nsISupportsPrimitive::TYPE_PRTIME: {
848 NS_WARNING("Unsupported primitive type used");
849 aArgv->setNull();
850 break;
852 default: {
853 NS_WARNING("Unknown primitive type used");
854 aArgv->setNull();
855 break;
858 return NS_OK;
861 nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
862 AutoJSAPI jsapi;
863 jsapi.Init();
864 JSContext* cx = jsapi.cx();
865 JSAutoRealm ar(cx, aGlobalObj);
867 return NS_OK;
870 bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
872 void nsJSContext::SetProcessingScriptTag(bool aFlag) {
873 mProcessingScriptTag = aFlag;
876 // static
877 void nsJSContext::SetLowMemoryState(bool aState) {
878 JSContext* cx = danger::GetJSContext();
879 JS::SetLowMemoryState(cx, aState);
882 static void GarbageCollectImpl(JS::GCReason aReason,
883 nsJSContext::IsShrinking aShrinking,
884 const JS::SliceBudget& aBudget) {
885 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
886 "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
888 bool wantIncremental = !aBudget.isUnlimited();
890 // We use danger::GetJSContext() since AutoJSAPI will assert if the current
891 // thread's context is null (such as during shutdown).
892 JSContext* cx = danger::GetJSContext();
894 if (!nsContentUtils::XPConnect() || !cx) {
895 return;
898 if (sScheduler->InIncrementalGC() && wantIncremental) {
899 // We're in the middle of incremental GC. Do another slice.
900 JS::PrepareForIncrementalGC(cx);
901 JS::IncrementalGCSlice(cx, aReason, aBudget);
902 return;
905 JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
906 ? JS::GCOptions::Shrink
907 : JS::GCOptions::Normal;
909 if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
910 sScheduler->SetNeedsFullGC();
913 if (sScheduler->NeedsFullGC()) {
914 JS::PrepareForFullGC(cx);
917 if (wantIncremental) {
918 // Incremental GC slices will be triggered by the GC Runner. If one doesn't
919 // already exist, create it in the GC_SLICE_END callback for the first
920 // slice being executed here.
921 JS::StartIncrementalGC(cx, options, aReason, aBudget);
922 } else {
923 JS::NonIncrementalGC(cx, options, aReason);
927 // static
928 void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
929 IsShrinking aShrinking) {
930 GarbageCollectImpl(aReason, aShrinking, JS::SliceBudget::unlimited());
933 // static
934 void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
935 IsShrinking aShrinking,
936 JS::SliceBudget& aBudget) {
937 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
938 GarbageCollectImpl(aReason, aShrinking, aBudget);
941 static void FinishAnyIncrementalGC() {
942 AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
944 if (sScheduler->InIncrementalGC()) {
945 AutoJSAPI jsapi;
946 jsapi.Init();
948 // We're in the middle of an incremental GC, so finish it.
949 JS::PrepareForIncrementalGC(jsapi.cx());
950 JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
954 static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
955 TimeStamp startTimeStamp = TimeStamp::Now();
956 FinishAnyIncrementalGC();
958 JS::SliceBudget budget =
959 sScheduler->ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
960 bool earlyForgetSkippable = sScheduler->IsEarlyForgetSkippable();
961 nsCycleCollector_forgetSkippable(startTimeStamp, budget, !aDeadline.IsNull(),
962 aRemoveChildless, earlyForgetSkippable);
963 TimeStamp now = TimeStamp::Now();
964 sScheduler->NoteForgetSkippableComplete(now,
965 nsCycleCollector_suspectedCount());
967 TimeDuration duration = now - startTimeStamp;
968 if (duration.ToSeconds()) {
969 TimeDuration idleDuration;
970 if (!aDeadline.IsNull()) {
971 if (aDeadline < now) {
972 // This slice overflowed the idle period.
973 if (aDeadline > startTimeStamp) {
974 idleDuration = aDeadline - startTimeStamp;
976 } else {
977 idleDuration = duration;
981 uint32_t percent =
982 uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
983 Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
987 static void MaybeLogStats(const CycleCollectorResults& aResults,
988 uint32_t aCleanups) {
989 if (!StaticPrefs::javascript_options_mem_log() && !sCCStats->mFile) {
990 return;
993 TimeDuration delta = GetCollectionTimeDelta();
995 nsCString mergeMsg;
996 if (aResults.mMergedZones) {
997 mergeMsg.AssignLiteral(" merged");
1000 nsCString gcMsg;
1001 if (aResults.mForcedGC) {
1002 gcMsg.AssignLiteral(", forced a GC");
1005 const char16_t* kFmt =
1006 u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
1007 u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
1008 u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
1009 u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
1010 u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
1011 nsString msg;
1012 nsTextFormatter::ssprintf(
1013 msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
1014 ProcessNameForCollectorLog(), getpid(),
1015 sCCStats->mMaxSliceTime.ToMilliseconds(),
1016 sCCStats->mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices,
1017 sCCStats->mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
1018 mergeMsg.get(), aResults.mFreedRefCounted, aResults.mFreedGCed,
1019 sScheduler->mCCollectedWaitingForGC,
1020 sScheduler->mCCollectedZonesWaitingForGC,
1021 sScheduler->mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
1022 sCCStats->mForgetSkippableBeforeCC,
1023 sCCStats->mMinForgetSkippableTime.ToMilliseconds(),
1024 sCCStats->mMaxForgetSkippableTime.ToMilliseconds(),
1025 sCCStats->mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1026 sCCStats->mTotalForgetSkippableTime.ToMilliseconds(),
1027 sCCStats->mMaxSkippableDuration.ToMilliseconds(),
1028 sCCStats->mRemovedPurples);
1029 if (StaticPrefs::javascript_options_mem_log()) {
1030 nsCOMPtr<nsIConsoleService> cs =
1031 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1032 if (cs) {
1033 cs->LogStringMessage(msg.get());
1036 if (sCCStats->mFile) {
1037 fprintf(sCCStats->mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
1041 static void MaybeNotifyStats(const CycleCollectorResults& aResults,
1042 TimeDuration aCCNowDuration, uint32_t aCleanups) {
1043 if (!StaticPrefs::javascript_options_mem_notify()) {
1044 return;
1047 const char16_t* kJSONFmt =
1048 u"{ \"timestamp\": %llu, "
1049 u"\"duration\": %.f, "
1050 u"\"max_slice_pause\": %.f, "
1051 u"\"total_slice_pause\": %.f, "
1052 u"\"max_finish_gc_duration\": %.f, "
1053 u"\"max_sync_skippable_duration\": %.f, "
1054 u"\"suspected\": %lu, "
1055 u"\"visited\": { "
1056 u"\"RCed\": %lu, "
1057 u"\"GCed\": %lu }, "
1058 u"\"collected\": { "
1059 u"\"RCed\": %lu, "
1060 u"\"GCed\": %lu }, "
1061 u"\"waiting_for_gc\": %lu, "
1062 u"\"zones_waiting_for_gc\": %lu, "
1063 u"\"short_living_objects_waiting_for_gc\": %lu, "
1064 u"\"forced_gc\": %d, "
1065 u"\"forget_skippable\": { "
1066 u"\"times_before_cc\": %lu, "
1067 u"\"min\": %.f, "
1068 u"\"max\": %.f, "
1069 u"\"avg\": %.f, "
1070 u"\"total\": %.f, "
1071 u"\"removed\": %lu } "
1072 u"}";
1074 nsString json;
1075 nsTextFormatter::ssprintf(
1076 json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
1077 sCCStats->mMaxSliceTime.ToMilliseconds(),
1078 sCCStats->mTotalSliceTime.ToMilliseconds(),
1079 sCCStats->mMaxGCDuration.ToMilliseconds(),
1080 sCCStats->mMaxSkippableDuration.ToMilliseconds(), sCCStats->mSuspected,
1081 aResults.mVisitedRefCounted, aResults.mVisitedGCed,
1082 aResults.mFreedRefCounted, aResults.mFreedGCed,
1083 sScheduler->mCCollectedWaitingForGC,
1084 sScheduler->mCCollectedZonesWaitingForGC,
1085 sScheduler->mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
1086 sCCStats->mForgetSkippableBeforeCC,
1087 sCCStats->mMinForgetSkippableTime.ToMilliseconds(),
1088 sCCStats->mMaxForgetSkippableTime.ToMilliseconds(),
1089 sCCStats->mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1090 sCCStats->mTotalForgetSkippableTime.ToMilliseconds(),
1091 sCCStats->mRemovedPurples);
1092 nsCOMPtr<nsIObserverService> observerService =
1093 mozilla::services::GetObserverService();
1094 if (observerService) {
1095 observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
1096 json.get());
1100 // static
1101 void nsJSContext::CycleCollectNow(CCReason aReason,
1102 nsICycleCollectorListener* aListener) {
1103 if (!NS_IsMainThread()) {
1104 return;
1107 AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
1109 PrepareForCycleCollectionSlice(aReason, TimeStamp());
1110 nsCycleCollector_collect(aReason, aListener);
1111 sCCStats->AfterCycleCollectionSlice();
1114 // static
1115 void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason,
1116 TimeStamp aDeadline) {
1117 TimeStamp beginTime = TimeStamp::Now();
1119 // Before we begin the cycle collection, make sure there is no active GC.
1120 TimeStamp afterGCTime;
1121 if (sScheduler->InIncrementalGC()) {
1122 FinishAnyIncrementalGC();
1123 afterGCTime = TimeStamp::Now();
1126 if (!sScheduler->IsCollectingCycles()) {
1127 sCCStats->PrepareForCycleCollection(beginTime);
1128 sScheduler->NoteCCBegin();
1131 sCCStats->AfterPrepareForCycleCollectionSlice(aDeadline, beginTime,
1132 afterGCTime);
1135 // static
1136 void nsJSContext::RunCycleCollectorSlice(CCReason aReason,
1137 TimeStamp aDeadline) {
1138 if (!NS_IsMainThread()) {
1139 return;
1142 PrepareForCycleCollectionSlice(aReason, aDeadline);
1144 // Decide how long we want to budget for this slice.
1145 if (sIncrementalCC) {
1146 bool preferShorterSlices;
1147 JS::SliceBudget budget = sScheduler->ComputeCCSliceBudget(
1148 aDeadline, sCCStats->mBeginTime, sCCStats->mEndSliceTime,
1149 TimeStamp::Now(), &preferShorterSlices);
1150 nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices);
1151 } else {
1152 JS::SliceBudget budget = JS::SliceBudget::unlimited();
1153 nsCycleCollector_collectSlice(budget, aReason, false);
1156 sCCStats->AfterCycleCollectionSlice();
1159 // static
1160 void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
1161 if (!NS_IsMainThread()) {
1162 return;
1165 AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
1167 PrepareForCycleCollectionSlice(CCReason::API, TimeStamp());
1169 JS::SliceBudget budget = JS::SliceBudget(JS::WorkBudget(aWorkBudget));
1170 nsCycleCollector_collectSlice(budget, CCReason::API);
1172 sCCStats->AfterCycleCollectionSlice();
1175 void nsJSContext::ClearMaxCCSliceTime() {
1176 sCCStats->mMaxSliceTimeSinceClear = TimeDuration();
1179 uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
1180 return sCCStats->mMaxSliceTimeSinceClear.ToMilliseconds();
1183 // static
1184 void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) {
1185 MOZ_ASSERT(NS_IsMainThread());
1187 TimeStamp startTime = TimeStamp::Now();
1188 sCCStats->PrepareForCycleCollection(startTime);
1190 // Run forgetSkippable synchronously to reduce the size of the CC graph. This
1191 // is particularly useful if we recently finished a GC.
1192 if (sScheduler->IsEarlyForgetSkippable()) {
1193 while (sScheduler->IsEarlyForgetSkippable()) {
1194 FireForgetSkippable(false, TimeStamp());
1196 sCCStats->AfterSyncForgetSkippable(startTime);
1199 if (sShuttingDown) {
1200 return;
1203 sScheduler->InitCCRunnerStateMachine(
1204 mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason);
1205 sScheduler->EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
1208 // static
1209 void nsJSContext::EndCycleCollectionCallback(
1210 const CycleCollectorResults& aResults) {
1211 MOZ_ASSERT(NS_IsMainThread());
1213 sScheduler->KillCCRunner();
1215 // Update timing information for the current slice before we log it, if
1216 // we previously called PrepareForCycleCollectionSlice(). During shutdown
1217 // CCs, this won't happen.
1218 sCCStats->AfterCycleCollectionSlice();
1220 TimeStamp endCCTimeStamp = TimeStamp::Now();
1221 MOZ_ASSERT(endCCTimeStamp >= sCCStats->mBeginTime);
1222 TimeDuration ccNowDuration = endCCTimeStamp - sCCStats->mBeginTime;
1223 TimeStamp prevCCEnd = sScheduler->GetLastCCEndTime();
1225 sScheduler->NoteCCEnd(aResults, endCCTimeStamp);
1227 // Log information about the CC via telemetry, JSON and the console.
1229 sCCStats->SendTelemetry(ccNowDuration, prevCCEnd);
1231 uint32_t cleanups = std::max(sCCStats->mForgetSkippableBeforeCC, 1u);
1233 MaybeLogStats(aResults, cleanups);
1235 MaybeNotifyStats(aResults, ccNowDuration, cleanups);
1237 // Update global state to indicate we have just run a cycle collection.
1238 sCCStats->Clear();
1240 // If we need a GC after this CC (typically because lots of GCed objects or
1241 // zones have been collected in the CC), schedule it.
1243 if (sScheduler->NeedsGCAfterCC()) {
1244 MOZ_ASSERT(
1245 TimeDuration::FromMilliseconds(
1246 StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration,
1247 "A max duration ICC shouldn't reduce GC delay to 0");
1249 TimeDuration delay;
1250 if (sScheduler->PreferFasterCollection()) {
1251 // If we collected lots of objects, trigger the next GC sooner so that
1252 // GC can cut JS-to-native edges and native objects can be then deleted.
1253 delay = TimeDuration::FromMilliseconds(
1254 StaticPrefs::javascript_options_gc_delay_interslice());
1255 } else {
1256 delay = TimeDuration::FromMilliseconds(
1257 StaticPrefs::javascript_options_gc_delay()) -
1258 std::min(ccNowDuration, kMaxICCDuration);
1261 sScheduler->PokeGC(JS::GCReason::CC_FINISHED, nullptr, delay);
1263 #if defined(MOZ_MEMORY)
1264 else if (
1265 StaticPrefs::
1266 dom_memory_foreground_content_processes_have_larger_page_cache()) {
1267 jemalloc_free_dirty_pages();
1269 #endif
1272 bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) {
1273 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
1275 if (!aDeadline) {
1276 mCurrentCollectionHasSeenNonIdle = true;
1277 } else if (mPreferFasterCollection) {
1278 // We found some idle time, try to utilize that a bit more given that
1279 // we're in a mode where idle time is rare.
1280 aDeadline = aDeadline + TimeDuration::FromMilliseconds(5.0);
1283 bool didDoWork = false;
1285 // The CC/GC scheduler (sScheduler) decides what action(s) to take during
1286 // this invocation of the CC runner.
1288 // This may be zero, one, or multiple actions. (Zero is when CC is blocked by
1289 // incremental GC, or when the scheduler determined that a CC is no longer
1290 // needed.) Loop until the scheduler finishes this invocation by returning
1291 // `Yield` in step.mYield.
1292 CCRunnerStep step;
1293 do {
1294 step = sScheduler->AdvanceCCRunner(aDeadline, TimeStamp::Now(),
1295 nsCycleCollector_suspectedCount());
1296 switch (step.mAction) {
1297 case CCRunnerAction::None:
1298 break;
1300 case CCRunnerAction::MinorGC:
1301 JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(),
1302 step.mParam.mReason);
1303 sScheduler->NoteMinorGCEnd();
1304 break;
1306 case CCRunnerAction::ForgetSkippable:
1307 // 'Forget skippable' only, then end this invocation.
1308 FireForgetSkippable(bool(step.mParam.mRemoveChildless), aDeadline);
1309 break;
1311 case CCRunnerAction::CleanupContentUnbinder:
1312 // Clear content unbinder before the first actual CC slice.
1313 Element::ClearContentUnbinder();
1314 break;
1316 case CCRunnerAction::CleanupDeferred:
1317 // and if time still permits, perform deferred deletions.
1318 nsCycleCollector_doDeferredDeletion();
1319 break;
1321 case CCRunnerAction::CycleCollect:
1322 // Cycle collection slice.
1323 nsJSContext::RunCycleCollectorSlice(step.mParam.mCCReason, aDeadline);
1324 break;
1326 case CCRunnerAction::StopRunning:
1327 // End this CC, either because we have run a cycle collection slice, or
1328 // because a CC is no longer needed.
1329 sScheduler->KillCCRunner();
1330 break;
1333 if (step.mAction != CCRunnerAction::None) {
1334 didDoWork = true;
1336 } while (step.mYield == CCRunnerYield::Continue);
1338 return didDoWork;
1341 // static
1342 bool nsJSContext::HasHadCleanupSinceLastGC() {
1343 return sScheduler->IsEarlyForgetSkippable(1);
1346 // static
1347 void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
1348 mozilla::TimeStamp aDeadline) {
1349 sScheduler->RunNextCollectorTimer(aReason, aDeadline);
1352 // static
1353 void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
1354 JS::GCReason aReason) {
1355 if (!aDocShell || !XRE_IsContentProcess()) {
1356 return;
1359 BrowsingContext* bc = aDocShell->GetBrowsingContext();
1360 if (!bc) {
1361 return;
1364 BrowsingContext* root = bc->Top();
1365 if (bc == root) {
1366 // We don't want to run collectors when loading the top level page.
1367 return;
1370 nsIDocShell* rootDocShell = root->GetDocShell();
1371 if (!rootDocShell) {
1372 return;
1375 Document* rootDocument = rootDocShell->GetDocument();
1376 if (!rootDocument ||
1377 rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
1378 rootDocument->IsInBackgroundWindow()) {
1379 return;
1382 PresShell* presShell = rootDocument->GetPresShell();
1383 if (!presShell) {
1384 return;
1387 nsViewManager* vm = presShell->GetViewManager();
1388 if (!vm) {
1389 return;
1392 if (!sScheduler->IsUserActive() &&
1393 (sScheduler->InIncrementalGC() || sScheduler->IsCollectingCycles())) {
1394 Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
1395 if (next.isSome()) {
1396 // Try to not delay the next RefreshDriver tick, so give a reasonable
1397 // deadline for collectors.
1398 sScheduler->RunNextCollectorTimer(aReason, next.value());
1402 nsCOMPtr<nsIDocShell> shell = aDocShell;
1403 NS_DispatchToCurrentThreadQueue(
1404 NS_NewRunnableFunction("nsJSContext::MaybeRunNextCollectorSlice",
1405 [shell] {
1406 nsIDocShell::BusyFlags busyFlags =
1407 nsIDocShell::BUSY_FLAGS_NONE;
1408 shell->GetBusyFlags(&busyFlags);
1409 if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) {
1410 return;
1413 // In order to improve performance on the next
1414 // page, run a minor GC. The 16ms limit ensures
1415 // it isn't called all the time if there are for
1416 // example multiple iframes loading at the same
1417 // time.
1418 JS::RunNurseryCollection(
1419 CycleCollectedJSRuntime::Get()->Runtime(),
1420 JS::GCReason::PREPARE_FOR_PAGELOAD,
1421 mozilla::TimeDuration::FromMilliseconds(16));
1423 EventQueuePriority::Idle);
1426 // static
1427 void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
1428 TimeDuration aDelay) {
1429 sScheduler->PokeGC(aReason, aObj, aDelay);
1432 // static
1433 void nsJSContext::MaybePokeGC() {
1434 if (sShuttingDown) {
1435 return;
1438 JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
1439 JS::GCReason reason = JS::WantEagerMinorGC(rt);
1440 if (reason != JS::GCReason::NO_REASON) {
1441 MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION);
1442 sScheduler->PokeMinorGC(reason);
1445 // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some
1446 // benchmarks. Hopefully that will be worked out and this will check for
1447 // whether an eager major GC is needed.
1450 void nsJSContext::DoLowMemoryGC() {
1451 if (sShuttingDown) {
1452 return;
1454 nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1455 nsJSContext::ShrinkingGC);
1456 nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE);
1457 if (sScheduler->NeedsGCAfterCC()) {
1458 nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1459 nsJSContext::ShrinkingGC);
1463 // static
1464 void nsJSContext::LowMemoryGC() {
1465 RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
1466 CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE);
1467 if (!mbPromise) {
1468 // Normally when the promise is null it means that IPC failed, that probably
1469 // means that something bad happened, don't bother with the GC.
1470 return;
1472 mbPromise->Then(
1473 GetMainThreadSerialEventTarget(), __func__,
1474 [](bool aIgnored) { DoLowMemoryGC(); },
1475 [](mozilla::ipc::ResponseRejectReason r) {});
1478 // static
1479 void nsJSContext::MaybePokeCC() {
1480 sScheduler->MaybePokeCC(TimeStamp::NowLoRes(),
1481 nsCycleCollector_suspectedCount());
1484 static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
1485 const JS::GCDescription& aDesc) {
1486 NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
1488 static TimeStamp sCurrentGCStartTime;
1490 switch (aProgress) {
1491 case JS::GC_CYCLE_BEGIN: {
1492 // Prevent cycle collections and shrinking during incremental GC.
1493 sScheduler->NoteGCBegin(aDesc.reason_);
1494 sCurrentGCStartTime = TimeStamp::Now();
1495 break;
1498 case JS::GC_CYCLE_END: {
1499 TimeDuration delta = GetCollectionTimeDelta();
1501 if (StaticPrefs::javascript_options_mem_log()) {
1502 nsString gcstats;
1503 gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
1504 nsAutoString prefix;
1505 nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
1506 delta.ToSeconds(),
1507 ProcessNameForCollectorLog(), getpid());
1508 nsString msg = prefix + gcstats;
1509 nsCOMPtr<nsIConsoleService> cs =
1510 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1511 if (cs) {
1512 cs->LogStringMessage(msg.get());
1516 sScheduler->NoteGCEnd();
1518 // May need to kill the GC runner
1519 sScheduler->KillGCRunner();
1521 nsJSContext::MaybePokeCC();
1523 #if defined(MOZ_MEMORY)
1524 bool freeDirty = false;
1525 #endif
1526 if (aDesc.isZone_) {
1527 sScheduler->PokeFullGC();
1528 } else {
1529 #if defined(MOZ_MEMORY)
1530 freeDirty = true;
1531 #endif
1532 sScheduler->SetNeedsFullGC(false);
1533 sScheduler->KillFullGCTimer();
1536 if (sScheduler->IsCCNeeded(TimeStamp::Now(),
1537 nsCycleCollector_suspectedCount()) !=
1538 CCReason::NO_REASON) {
1539 #if defined(MOZ_MEMORY)
1540 // We're likely to free the dirty pages after CC.
1541 freeDirty = false;
1542 #endif
1543 nsCycleCollector_dispatchDeferredDeletion();
1546 MOZ_ASSERT(sCurrentGCStartTime);
1547 Telemetry::Accumulate(
1548 Telemetry::GC_IN_PROGRESS_MS,
1549 (TimeStamp::Now() - sCurrentGCStartTime).ToMilliseconds());
1551 #if defined(MOZ_MEMORY)
1552 if (freeDirty &&
1553 StaticPrefs::
1554 dom_memory_foreground_content_processes_have_larger_page_cache()) {
1555 jemalloc_free_dirty_pages();
1557 #endif
1558 break;
1561 case JS::GC_SLICE_BEGIN:
1562 break;
1564 case JS::GC_SLICE_END:
1565 sScheduler->NoteGCSliceEnd(aDesc.lastSliceStart(aCx),
1566 aDesc.lastSliceEnd(aCx));
1568 if (sShuttingDown) {
1569 sScheduler->KillGCRunner();
1570 } else {
1571 // If incremental GC wasn't triggered by GCTimerFired, we may not have a
1572 // runner to ensure all the slices are handled. So, create the runner
1573 // here.
1574 sScheduler->EnsureOrResetGCRunner();
1577 if (sScheduler->IsCCNeeded(TimeStamp::Now(),
1578 nsCycleCollector_suspectedCount()) !=
1579 CCReason::NO_REASON) {
1580 nsCycleCollector_dispatchDeferredDeletion();
1583 if (StaticPrefs::javascript_options_mem_log()) {
1584 nsString gcstats;
1585 gcstats.Adopt(aDesc.formatSliceMessage(aCx));
1586 nsAutoString prefix;
1587 nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
1588 ProcessNameForCollectorLog(), getpid());
1589 nsString msg = prefix + gcstats;
1590 nsCOMPtr<nsIConsoleService> cs =
1591 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1592 if (cs) {
1593 cs->LogStringMessage(msg.get());
1597 break;
1599 default:
1600 MOZ_CRASH("Unexpected GCProgress value");
1603 if (sPrevGCSliceCallback) {
1604 (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
1608 void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
1609 mWindowProxy = aWindowProxy;
1612 JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
1614 void nsJSContext::LikelyShortLivingObjectCreated() {
1615 ++sScheduler->mLikelyShortLivingObjectsNeedingGC;
1618 void mozilla::dom::StartupJSEnvironment() {
1619 // initialize all our statics, so that we can restart XPCOM
1620 sIsInitialized = false;
1621 sShuttingDown = false;
1622 sCCStats = CycleCollectorStats::Get();
1625 static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
1626 AutoJSAPI jsapi;
1627 jsapi.Init();
1628 JS_SetGCParameter(jsapi.cx(), aParam, aValue);
1631 static void ResetGCParameter(JSGCParamKey aParam) {
1632 AutoJSAPI jsapi;
1633 jsapi.Init();
1634 JS_ResetGCParameter(jsapi.cx(), aParam);
1637 static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
1638 void* aClosure) {
1639 int32_t prefMB = Preferences::GetInt(aPrefName, -1);
1640 // handle overflow and negative pref values
1641 CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
1642 if (prefB.isValid() && prefB.value() >= 0) {
1643 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1644 } else {
1645 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1649 static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
1650 void* aClosure) {
1651 int32_t prefKB = Preferences::GetInt(aPrefName, -1);
1652 // handle overflow and negative pref values
1653 CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
1654 if (prefB.isValid() && prefB.value() >= 0) {
1655 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1656 } else {
1657 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1661 static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
1662 void* aClosure) {
1663 int32_t pref = Preferences::GetInt(aPrefName, -1);
1664 // handle overflow and negative pref values
1665 if (pref >= 0 && pref < 10000) {
1666 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1667 } else {
1668 ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1672 static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
1673 void* aClosure) {
1674 bool pref = Preferences::GetBool(aPrefName);
1675 SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1678 static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
1679 void* aClosure) {
1680 int32_t pref = Preferences::GetInt(aPrefName, -1);
1681 // handle overflow and negative pref values
1682 if (pref > 0 && pref < 100000) {
1683 sScheduler->SetActiveIntersliceGCBudget(
1684 TimeDuration::FromMilliseconds(pref));
1685 SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
1686 } else {
1687 ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
1691 static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
1692 void* aClosure) {
1693 bool pref = Preferences::GetBool(aPrefName);
1694 sIncrementalCC = pref;
1697 class JSDispatchableRunnable final : public Runnable {
1698 ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
1700 public:
1701 explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
1702 : mozilla::Runnable("JSDispatchableRunnable"),
1703 mDispatchable(aDispatchable) {
1704 MOZ_ASSERT(mDispatchable);
1707 protected:
1708 NS_IMETHOD Run() override {
1709 MOZ_ASSERT(NS_IsMainThread());
1711 AutoJSAPI jsapi;
1712 jsapi.Init();
1714 JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
1715 sShuttingDown ? JS::Dispatchable::ShuttingDown
1716 : JS::Dispatchable::NotShuttingDown;
1718 mDispatchable->run(jsapi.cx(), maybeShuttingDown);
1719 mDispatchable = nullptr; // mDispatchable may delete itself
1721 return NS_OK;
1724 private:
1725 JS::Dispatchable* mDispatchable;
1728 static bool DispatchToEventLoop(void* closure,
1729 JS::Dispatchable* aDispatchable) {
1730 MOZ_ASSERT(!closure);
1732 // This callback may execute either on the main thread or a random JS-internal
1733 // helper thread. This callback can be called during shutdown so we cannot
1734 // simply NS_DispatchToMainThread. Failure during shutdown is expected and
1735 // properly handled by the JS engine.
1737 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
1738 if (!mainTarget) {
1739 return false;
1742 RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
1743 MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
1744 return true;
1747 static bool ConsumeStream(JSContext* aCx, JS::Handle<JSObject*> aObj,
1748 JS::MimeType aMimeType,
1749 JS::StreamConsumer* aConsumer) {
1750 return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
1751 nullptr);
1754 static JS::SliceBudget CreateGCSliceBudget(JS::GCReason aReason,
1755 int64_t aMillis) {
1756 return sScheduler->CreateGCSliceBudget(
1757 mozilla::TimeDuration::FromMilliseconds(aMillis), false, false);
1760 void nsJSContext::EnsureStatics() {
1761 if (sIsInitialized) {
1762 if (!nsContentUtils::XPConnect()) {
1763 MOZ_CRASH();
1765 return;
1768 // Let's make sure that our main thread is the same as the xpcom main thread.
1769 MOZ_ASSERT(NS_IsMainThread());
1771 sScheduler =
1772 new (&sSchedulerStorage) CCGCScheduler(); // Reset the scheduler state.
1774 AutoJSAPI jsapi;
1775 jsapi.Init();
1777 sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
1779 JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget);
1781 JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
1782 JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
1783 FetchUtil::ReportJSStreamError);
1785 // Set these global xpconnect options...
1786 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
1787 "javascript.options.mem.max",
1788 (void*)JSGC_MAX_BYTES);
1789 Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
1790 "javascript.options.mem.nursery.min_kb",
1791 (void*)JSGC_MIN_NURSERY_BYTES);
1792 Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
1793 "javascript.options.mem.nursery.max_kb",
1794 (void*)JSGC_MAX_NURSERY_BYTES);
1796 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1797 "javascript.options.mem.gc_per_zone",
1798 (void*)JSGC_PER_ZONE_GC_ENABLED);
1800 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1801 "javascript.options.mem.gc_incremental",
1802 (void*)JSGC_INCREMENTAL_GC_ENABLED);
1804 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1805 "javascript.options.mem.gc_generational",
1806 (void*)JSGC_NURSERY_ENABLED);
1808 Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1809 "javascript.options.mem.gc_compacting",
1810 (void*)JSGC_COMPACTING_ENABLED);
1812 #ifdef NIGHTLY_BUILD
1813 Preferences::RegisterCallbackAndCall(
1814 SetMemoryPrefChangedCallbackBool,
1815 "javascript.options.mem.gc_experimental_semispace_nursery",
1816 (void*)JSGC_SEMISPACE_NURSERY_ENABLED);
1817 #endif
1819 Preferences::RegisterCallbackAndCall(
1820 SetMemoryPrefChangedCallbackBool,
1821 "javascript.options.mem.gc_parallel_marking",
1822 (void*)JSGC_PARALLEL_MARKING_ENABLED);
1824 Preferences::RegisterCallbackAndCall(
1825 SetMemoryPrefChangedCallbackInt,
1826 "javascript.options.mem.gc_parallel_marking_threshold_mb",
1827 (void*)JSGC_PARALLEL_MARKING_THRESHOLD_MB);
1829 Preferences::RegisterCallbackAndCall(
1830 SetMemoryPrefChangedCallbackInt,
1831 "javascript.options.mem.gc_max_parallel_marking_threads",
1832 (void*)JSGC_MAX_MARKING_THREADS);
1834 Preferences::RegisterCallbackAndCall(
1835 SetMemoryGCSliceTimePrefChangedCallback,
1836 "javascript.options.mem.gc_incremental_slice_ms");
1838 Preferences::RegisterCallbackAndCall(
1839 SetMemoryPrefChangedCallbackBool,
1840 "javascript.options.mem.incremental_weakmap",
1841 (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
1843 Preferences::RegisterCallbackAndCall(
1844 SetMemoryPrefChangedCallbackInt,
1845 "javascript.options.mem.gc_high_frequency_time_limit_ms",
1846 (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
1848 Preferences::RegisterCallbackAndCall(
1849 SetMemoryPrefChangedCallbackInt,
1850 "javascript.options.mem.gc_low_frequency_heap_growth",
1851 (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
1853 Preferences::RegisterCallbackAndCall(
1854 SetMemoryPrefChangedCallbackInt,
1855 "javascript.options.mem.gc_high_frequency_large_heap_growth",
1856 (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
1858 Preferences::RegisterCallbackAndCall(
1859 SetMemoryPrefChangedCallbackInt,
1860 "javascript.options.mem.gc_high_frequency_small_heap_growth",
1861 (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
1863 Preferences::RegisterCallbackAndCall(
1864 SetMemoryPrefChangedCallbackBool,
1865 "javascript.options.mem.gc_balanced_heap_limits",
1866 (void*)JSGC_BALANCED_HEAP_LIMITS_ENABLED);
1868 Preferences::RegisterCallbackAndCall(
1869 SetMemoryPrefChangedCallbackInt,
1870 "javascript.options.mem.gc_heap_growth_factor",
1871 (void*)JSGC_HEAP_GROWTH_FACTOR);
1873 Preferences::RegisterCallbackAndCall(
1874 SetMemoryPrefChangedCallbackInt,
1875 "javascript.options.mem.gc_small_heap_size_max_mb",
1876 (void*)JSGC_SMALL_HEAP_SIZE_MAX);
1878 Preferences::RegisterCallbackAndCall(
1879 SetMemoryPrefChangedCallbackInt,
1880 "javascript.options.mem.gc_large_heap_size_min_mb",
1881 (void*)JSGC_LARGE_HEAP_SIZE_MIN);
1883 Preferences::RegisterCallbackAndCall(
1884 SetMemoryPrefChangedCallbackInt,
1885 "javascript.options.mem.gc_allocation_threshold_mb",
1886 (void*)JSGC_ALLOCATION_THRESHOLD);
1888 Preferences::RegisterCallbackAndCall(
1889 SetMemoryPrefChangedCallbackInt,
1890 "javascript.options.mem.gc_malloc_threshold_base_mb",
1891 (void*)JSGC_MALLOC_THRESHOLD_BASE);
1893 Preferences::RegisterCallbackAndCall(
1894 SetMemoryPrefChangedCallbackInt,
1895 "javascript.options.mem.gc_small_heap_incremental_limit",
1896 (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
1897 Preferences::RegisterCallbackAndCall(
1898 SetMemoryPrefChangedCallbackInt,
1899 "javascript.options.mem.gc_large_heap_incremental_limit",
1900 (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
1902 Preferences::RegisterCallbackAndCall(
1903 SetMemoryPrefChangedCallbackInt,
1904 "javascript.options.mem.gc_urgent_threshold_mb",
1905 (void*)JSGC_URGENT_THRESHOLD_MB);
1907 Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
1908 "dom.cycle_collector.incremental");
1910 Preferences::RegisterCallbackAndCall(
1911 SetMemoryPrefChangedCallbackInt,
1912 "javascript.options.mem.gc_min_empty_chunk_count",
1913 (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
1915 Preferences::RegisterCallbackAndCall(
1916 SetMemoryPrefChangedCallbackInt,
1917 "javascript.options.mem.gc_helper_thread_ratio",
1918 (void*)JSGC_HELPER_THREAD_RATIO);
1920 Preferences::RegisterCallbackAndCall(
1921 SetMemoryPrefChangedCallbackInt,
1922 "javascript.options.mem.gc_max_helper_threads",
1923 (void*)JSGC_MAX_HELPER_THREADS);
1925 Preferences::RegisterCallbackAndCall(
1926 SetMemoryPrefChangedCallbackInt,
1927 "javascript.options.mem.nursery_eager_collection_threshold_kb",
1928 (void*)JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_KB);
1930 Preferences::RegisterCallbackAndCall(
1931 SetMemoryPrefChangedCallbackInt,
1932 "javascript.options.mem.nursery_eager_collection_threshold_percent",
1933 (void*)JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_PERCENT);
1935 Preferences::RegisterCallbackAndCall(
1936 SetMemoryPrefChangedCallbackInt,
1937 "javascript.options.mem.nursery_eager_collection_timeout_ms",
1938 (void*)JSGC_NURSERY_EAGER_COLLECTION_TIMEOUT_MS);
1940 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1941 if (!obs) {
1942 MOZ_CRASH();
1945 nsIObserver* observer = new nsJSEnvironmentObserver();
1946 obs->AddObserver(observer, "memory-pressure", false);
1947 obs->AddObserver(observer, "user-interaction-inactive", false);
1948 obs->AddObserver(observer, "user-interaction-active", false);
1949 obs->AddObserver(observer, "quit-application", false);
1950 obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
1951 obs->AddObserver(observer, "content-child-will-shutdown", false);
1953 sIsInitialized = true;
1956 void mozilla::dom::ShutdownJSEnvironment() {
1957 sShuttingDown = true;
1958 sScheduler->Shutdown();
1959 sCCStats = nullptr;
1962 AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
1963 : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
1965 void AsyncErrorReporter::SerializeStack(JSContext* aCx,
1966 JS::Handle<JSObject*> aStack) {
1967 mStackHolder = MakeUnique<SerializedStackHolder>();
1968 mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
1971 void AsyncErrorReporter::SetException(JSContext* aCx,
1972 JS::Handle<JS::Value> aException) {
1973 MOZ_ASSERT(NS_IsMainThread());
1974 mException.init(aCx, aException);
1975 mHasException = true;
1978 NS_IMETHODIMP AsyncErrorReporter::Run() {
1979 AutoJSAPI jsapi;
1980 // We're only using this context to deserialize a stack to report to the
1981 // console, so the scope we use doesn't matter. Stack frame filtering happens
1982 // based on the principal encoded into the frame and the caller compartment,
1983 // not the compartment of the frame object, and the console reporting code
1984 // will not be using our context, and therefore will not care what compartment
1985 // it has entered.
1986 DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
1987 MOZ_ASSERT(ok, "Problem with system global?");
1988 JSContext* cx = jsapi.cx();
1989 JS::Rooted<JSObject*> stack(cx);
1990 JS::Rooted<JSObject*> stackGlobal(cx);
1991 if (mStackHolder) {
1992 stack = mStackHolder->ReadStack(cx);
1993 if (stack) {
1994 stackGlobal = JS::CurrentGlobalOrNull(cx);
1998 JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
1999 if (mHasException) {
2000 MOZ_ASSERT(NS_IsMainThread());
2001 exception = Some(mException);
2002 // Remove our reference to the exception.
2003 mException.setUndefined();
2004 mHasException = false;
2007 mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
2008 return NS_OK;
2011 // A fast-array class for JS. This class supports both nsIJSScriptArray and
2012 // nsIArray. If it is JS itself providing and consuming this class, all work
2013 // can be done via nsIJSScriptArray, and avoid the conversion of elements
2014 // to/from nsISupports.
2015 // When consumed by non-JS (eg, another script language), conversion is done
2016 // on-the-fly.
2017 class nsJSArgArray final : public nsIJSArgArray {
2018 public:
2019 nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
2020 nsresult* prv);
2022 // nsISupports
2023 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
2024 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
2025 nsIJSArgArray)
2027 // nsIArray
2028 NS_DECL_NSIARRAY
2030 // nsIJSArgArray
2031 nsresult GetArgs(uint32_t* argc, void** argv) override;
2033 void ReleaseJSObjects();
2035 protected:
2036 ~nsJSArgArray();
2037 JSContext* mContext;
2038 JS::Heap<JS::Value>* mArgv;
2039 uint32_t mArgc;
2042 nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
2043 const JS::Value* argv, nsresult* prv)
2044 : mContext(aContext), mArgv(nullptr), mArgc(argc) {
2045 // copy the array - we don't know its lifetime, and ours is tied to xpcom
2046 // refcounting.
2047 if (argc) {
2048 mArgv = new (fallible) JS::Heap<JS::Value>[argc];
2049 if (!mArgv) {
2050 *prv = NS_ERROR_OUT_OF_MEMORY;
2051 return;
2055 // Callers are allowed to pass in a null argv even for argc > 0. They can
2056 // then use GetArgs to initialize the values.
2057 if (argv) {
2058 for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
2061 if (argc > 0) {
2062 mozilla::HoldJSObjects(this);
2065 *prv = NS_OK;
2068 nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
2070 void nsJSArgArray::ReleaseJSObjects() {
2071 delete[] mArgv;
2073 if (mArgc > 0) {
2074 mArgc = 0;
2075 mozilla::DropJSObjects(this);
2079 // QueryInterface implementation for nsJSArgArray
2080 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSArgArray)
2082 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
2083 tmp->ReleaseJSObjects();
2084 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2085 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
2086 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2088 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
2089 if (tmp->mArgv) {
2090 for (uint32_t i = 0; i < tmp->mArgc; ++i) {
2091 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
2094 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2096 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
2097 NS_INTERFACE_MAP_ENTRY(nsIArray)
2098 NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
2099 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
2100 NS_INTERFACE_MAP_END
2102 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
2103 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
2105 nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
2106 *argv = (void*)mArgv;
2107 *argc = mArgc;
2108 return NS_OK;
2111 // nsIArray impl
2112 NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
2113 *aLength = mArgc;
2114 return NS_OK;
2117 NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
2118 void** result) {
2119 *result = nullptr;
2120 if (index >= mArgc) return NS_ERROR_INVALID_ARG;
2122 if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
2123 uuid.Equals(NS_GET_IID(nsISupports))) {
2124 // Have to copy a Heap into a Rooted to work with it.
2125 JS::Rooted<JS::Value> val(mContext, mArgv[index]);
2126 return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
2127 (nsIVariant**)result);
2129 NS_WARNING("nsJSArgArray only handles nsIVariant");
2130 return NS_ERROR_NO_INTERFACE;
2133 NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
2134 uint32_t* _retval) {
2135 return NS_ERROR_NOT_IMPLEMENTED;
2138 NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
2139 uint8_t aArgc,
2140 nsISimpleEnumerator** aResult) {
2141 return NS_ERROR_NOT_IMPLEMENTED;
2144 NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
2145 nsISimpleEnumerator** _retval) {
2146 return NS_ERROR_NOT_IMPLEMENTED;
2149 // The factory function
2150 nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
2151 const JS::Value* argv, nsIJSArgArray** aArray) {
2152 nsresult rv;
2153 nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
2154 if (NS_FAILED(rv)) {
2155 return rv;
2157 ret.forget(aArray);
2158 return NS_OK;