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/. */
6 
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 "nsIContent.h"
31 #include "nsCycleCollector.h"
32 #include "nsXPCOMCIDInternal.h"
33 #include "nsTextFormatter.h"
34 #ifdef XP_WIN
35 #  include <process.h>
36 #  define getpid _getpid
37 #else
38 #  include <unistd.h>  // for getpid()
39 #endif
40 #include "xpcpublic.h"
41 
42 #include "jsapi.h"
43 #include "js/Array.h"  // JS::NewArrayObject
44 #include "js/PropertySpec.h"
45 #include "js/SliceBudget.h"
46 #include "js/Wrapper.h"
47 #include "nsIArray.h"
48 #include "WrapperFactory.h"
49 #include "nsGlobalWindow.h"
50 #include "mozilla/AutoRestore.h"
51 #include "mozilla/MainThreadIdlePeriod.h"
52 #include "mozilla/PresShell.h"
53 #include "mozilla/SchedulerGroup.h"
54 #include "mozilla/StaticPrefs_javascript.h"
55 #include "mozilla/StaticPtr.h"
56 #include "mozilla/dom/BrowsingContext.h"
57 #include "mozilla/dom/DOMException.h"
58 #include "mozilla/dom/DOMExceptionBinding.h"
59 #include "mozilla/dom/Element.h"
60 #include "mozilla/dom/ErrorEvent.h"
61 #include "mozilla/dom/FetchUtil.h"
62 #include "mozilla/dom/ScriptSettings.h"
63 #include "mozilla/dom/SerializedStackHolder.h"
64 #include "mozilla/CycleCollectedJSRuntime.h"
65 #include "nsRefreshDriver.h"
66 #include "nsJSPrincipals.h"
67 
68 #ifdef XP_MACOSX
69 // AssertMacros.h defines 'check' and conflicts with AccessCheck.h
70 #  undef check
71 #endif
72 #include "AccessCheck.h"
73 
74 #include "mozilla/Logging.h"
75 #include "prthread.h"
76 
77 #include "mozilla/Preferences.h"
78 #include "mozilla/Telemetry.h"
79 #include "mozilla/dom/BindingUtils.h"
80 #include "mozilla/Attributes.h"
81 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
82 #include "mozilla/ContentEvents.h"
83 #include "mozilla/CycleCollectedJSContext.h"
84 #include "nsCycleCollectionNoteRootCallback.h"
85 #include "GeckoProfiler.h"
86 #include "mozilla/IdleTaskRunner.h"
87 #include "nsViewManager.h"
88 #include "mozilla/EventStateManager.h"
89 
90 using namespace mozilla;
91 using namespace mozilla::dom;
92 
93 // Thank you Microsoft!
94 #ifdef CompareString
95 #  undef CompareString
96 #endif
97 
98 // The amount of time we wait between a request to CC (after GC ran)
99 // and doing the actual CC.
100 static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6);
101 
102 static const TimeDuration kCCSkippableDelay =
103     TimeDuration::FromMilliseconds(250);
104 
105 // In case the cycle collector isn't run at all, we don't want forget skippables
106 // to run too often. So limit the forget skippable cycle to start at earliest 2
107 // seconds after the end of the previous cycle.
108 static const TimeDuration kTimeBetweenForgetSkippableCycles =
109     TimeDuration::FromSeconds(2);
110 
111 // ForgetSkippable is usually fast, so we can use small budgets.
112 // This isn't a real budget but a hint to IdleTaskRunner whether there
113 // is enough time to call ForgetSkippable.
114 static const TimeDuration kForgetSkippableSliceDuration =
115     TimeDuration::FromMilliseconds(2);
116 
117 // Maximum amount of time that should elapse between incremental CC slices
118 static const TimeDuration kICCIntersliceDelay =
119     TimeDuration::FromMilliseconds(64);
120 
121 // Time budget for an incremental CC slice when using timer to run it.
122 static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3);
123 // Minimum budget for an incremental CC slice when using idle time to run it.
124 static const TimeDuration kIdleICCSliceBudget =
125     TimeDuration::FromMilliseconds(2);
126 
127 // Maximum total duration for an ICC
128 static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2);
129 
130 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
131 // objects in the purple buffer.
132 static const TimeDuration kCCForced =
133     TimeDuration::FromSeconds(2 * 60);  // 2 min
134 static const uint32_t kCCForcedPurpleLimit = 10;
135 
136 // Don't allow an incremental GC to lock out the CC for too long.
137 static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
138 
139 // Trigger a CC if the purple buffer exceeds this size when we check it.
140 static const uint32_t kCCPurpleLimit = 200;
141 
142 // if you add statics here, add them to the list in StartupJSEnvironment
143 
144 enum class CCRunnerState { Inactive, EarlyTimer, LateTimer, FinalTimer };
145 
146 static nsITimer* sGCTimer;
147 static nsITimer* sShrinkingGCTimer;
148 static StaticRefPtr<IdleTaskRunner> sCCRunner;
149 static StaticRefPtr<IdleTaskRunner> sICCRunner;
150 static nsITimer* sFullGCTimer;
151 static StaticRefPtr<IdleTaskRunner> sInterSliceGCRunner;
152 
153 static TimeStamp sLastCCEndTime;
154 
155 static TimeStamp sLastForgetSkippableCycleEndTime;
156 
157 static TimeStamp sCurrentGCStartTime;
158 
159 static CCRunnerState sCCRunnerState = CCRunnerState::Inactive;
160 static TimeDuration sCCDelay = kCCDelay;
161 static bool sCCLockedOut;
162 static TimeStamp sCCLockedOutTime;
163 
164 static JS::GCSliceCallback sPrevGCSliceCallback;
165 
166 static bool sHasRunGC;
167 
168 static uint32_t sCCollectedWaitingForGC;
169 static uint32_t sCCollectedZonesWaitingForGC;
170 static uint32_t sLikelyShortLivingObjectsNeedingGC;
171 static int32_t sCCRunnerEarlyFireCount = 0;
172 static uint32_t sPreviousSuspectedCount = 0;
173 static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
174 static bool sNeedsFullCC = false;
175 static bool sNeedsFullGC = false;
176 static bool sNeedsGCAfterCC = false;
177 static bool sIncrementalCC = false;
178 static TimeDuration sActiveIntersliceGCBudget =
179     TimeDuration::FromMilliseconds(5);
180 
181 static TimeStamp sFirstCollectionTime;
182 
183 static bool sIsInitialized;
184 static bool sDidShutdown;
185 static bool sShuttingDown;
186 
187 // nsJSEnvironmentObserver observes the user-interaction-inactive notifications
188 // and triggers a shrinking a garbage collection if the user is still inactive
189 // after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set.
190 
191 static bool sIsCompactingOnUserInactive = false;
192 
193 static TimeDuration sGCUnnotifiedTotalTime;
194 
195 struct CycleCollectorStats {
196   constexpr CycleCollectorStats() = default;
197   void Init();
198   void Clear();
199   void PrepareForCycleCollectionSlice(TimeStamp aDeadline = TimeStamp());
200   void FinishCycleCollectionSlice();
201   void RunForgetSkippable();
202   void UpdateAfterForgetSkippable(TimeDuration duration,
203                                   uint32_t aRemovedPurples);
204   void UpdateAfterCycleCollection();
205 
206   void SendTelemetry(TimeDuration aCCNowDuration) const;
207   void MaybeLogStats(const CycleCollectorResults& aResults,
208                      uint32_t aCleanups) const;
209   void MaybeNotifyStats(const CycleCollectorResults& aResults,
210                         TimeDuration aCCNowDuration, uint32_t aCleanups) const;
211 
212   // Time the current slice began, including any GC finishing.
213   TimeStamp mBeginSliceTime;
214 
215   // Time the previous slice of the current CC ended.
216   TimeStamp mEndSliceTime;
217 
218   // Time the current cycle collection began.
219   TimeStamp mBeginTime;
220 
221   // The longest GC finishing duration for any slice of the current CC.
222   TimeDuration mMaxGCDuration;
223 
224   // True if we ran sync forget skippable in any slice of the current CC.
225   bool mRanSyncForgetSkippable = false;
226 
227   // Number of suspected objects at the start of the current CC.
228   uint32_t mSuspected = 0;
229 
230   // The longest duration spent on sync forget skippable in any slice of the
231   // current CC.
232   TimeDuration mMaxSkippableDuration;
233 
234   // The longest pause of any slice in the current CC.
235   TimeDuration mMaxSliceTime;
236 
237   // The longest slice time since ClearMaxCCSliceTime() was called.
238   TimeDuration mMaxSliceTimeSinceClear;
239 
240   // The total amount of time spent actually running the current CC.
241   TimeDuration mTotalSliceTime;
242 
243   // True if we were locked out by the GC in any slice of the current CC.
244   bool mAnyLockedOut = false;
245 
246   // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
247   FILE* mFile = nullptr;
248 
249   // In case CC slice was triggered during idle time, set to the end of the idle
250   // period.
251   TimeStamp mIdleDeadline;
252 
253   TimeDuration mMinForgetSkippableTime;
254   TimeDuration mMaxForgetSkippableTime;
255   TimeDuration mTotalForgetSkippableTime;
256   uint32_t mForgetSkippableBeforeCC = 0;
257 
258   uint32_t mRemovedPurples = 0;
259 };
260 
261 static CycleCollectorStats sCCStats;
262 
ProcessNameForCollectorLog()263 static const char* ProcessNameForCollectorLog() {
264   return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
265                                                           : "content";
266 }
267 
268 namespace xpc {
269 
270 // This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
271 // Exceptions, and arbitrary values that were associated with a stack by the
272 // JS engine when they were thrown, as specified by exceptionStack.
273 //
274 // Note that the returned stackObj and stackGlobal are _not_ wrapped into the
275 // compartment of exceptionValue.
FindExceptionStackForConsoleReport(nsPIDOMWindowInner * win,JS::HandleValue exceptionValue,JS::HandleObject exceptionStack,JS::MutableHandleObject stackObj,JS::MutableHandleObject stackGlobal)276 void FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
277                                         JS::HandleValue exceptionValue,
278                                         JS::HandleObject exceptionStack,
279                                         JS::MutableHandleObject stackObj,
280                                         JS::MutableHandleObject stackGlobal) {
281   stackObj.set(nullptr);
282   stackGlobal.set(nullptr);
283 
284   if (!exceptionValue.isObject()) {
285     // Use the stack provided by the JS engine, if available. This will not be
286     // a wrapper.
287     if (exceptionStack) {
288       stackObj.set(exceptionStack);
289       stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
290     }
291     return;
292   }
293 
294   if (win && win->AsGlobal()->IsDying()) {
295     // Pretend like we have no stack, so we don't end up keeping the global
296     // alive via the stack.
297     return;
298   }
299 
300   JS::RootingContext* rcx = RootingCx();
301   JS::RootedObject exceptionObject(rcx, &exceptionValue.toObject());
302   if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
303     // At this point we know exceptionObject is a possibly-wrapped
304     // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
305     // but excStack must be same-compartment with the unwrapped ErrorObject.
306     // Return the ErrorObject's global as stackGlobal. This matches what we do
307     // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
308     // are same-compartment.
309     JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
310     stackObj.set(excStack);
311     stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
312     return;
313   }
314 
315   // It is not a JS Exception, try DOM Exception.
316   RefPtr<Exception> exception;
317   UNWRAP_OBJECT(DOMException, exceptionObject, exception);
318   if (!exception) {
319     // Not a DOM Exception, try XPC Exception.
320     UNWRAP_OBJECT(Exception, exceptionObject, exception);
321     if (!exception) {
322       // As above, use the stack provided by the JS engine, if available.
323       if (exceptionStack) {
324         stackObj.set(exceptionStack);
325         stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
326       }
327       return;
328     }
329   }
330 
331   nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
332   if (!stack) {
333     return;
334   }
335   JS::RootedValue value(rcx);
336   stack->GetNativeSavedFrame(&value);
337   if (value.isObject()) {
338     stackObj.set(&value.toObject());
339     MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
340     stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
341     return;
342   }
343 }
344 
345 } /* namespace xpc */
346 
GetCollectionTimeDelta()347 static TimeDuration GetCollectionTimeDelta() {
348   TimeStamp now = TimeStamp::Now();
349   if (sFirstCollectionTime) {
350     return now - sFirstCollectionTime;
351   }
352   sFirstCollectionTime = now;
353   return TimeDuration();
354 }
355 
KillTimers()356 static void KillTimers() {
357   nsJSContext::KillGCTimer();
358   nsJSContext::KillShrinkingGCTimer();
359   nsJSContext::KillCCRunner();
360   nsJSContext::KillICCRunner();
361   nsJSContext::KillFullGCTimer();
362   nsJSContext::KillInterSliceGCRunner();
363 }
364 
365 // If we collected a substantial amount of cycles, poke the GC since more
366 // objects might be unreachable now.
NeedsGCAfterCC()367 static bool NeedsGCAfterCC() {
368   return sCCollectedWaitingForGC > 250 || sCCollectedZonesWaitingForGC > 0 ||
369          sLikelyShortLivingObjectsNeedingGC > 2500 || sNeedsGCAfterCC;
370 }
371 
372 class nsJSEnvironmentObserver final : public nsIObserver {
373   ~nsJSEnvironmentObserver() = default;
374 
375  public:
376   NS_DECL_ISUPPORTS
377   NS_DECL_NSIOBSERVER
378 };
379 
NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver,nsIObserver)380 NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
381 
382 NS_IMETHODIMP
383 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
384                                  const char16_t* aData) {
385   if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
386     if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
387       if (sShuttingDown) {
388         // Don't GC/CC if we're already shutting down.
389         return NS_OK;
390       }
391       nsDependentString data(aData);
392       if (data.EqualsLiteral("low-memory-ongoing")) {
393         // Don't GC/CC if we are in an ongoing low-memory state since its very
394         // slow and it likely won't help us anyway.
395         return NS_OK;
396       }
397       if (data.EqualsLiteral("low-memory")) {
398         nsJSContext::SetLowMemoryState(true);
399       }
400       nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
401                                      nsJSContext::NonIncrementalGC,
402                                      nsJSContext::ShrinkingGC);
403       nsJSContext::CycleCollectNow();
404       if (NeedsGCAfterCC()) {
405         nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
406                                        nsJSContext::NonIncrementalGC,
407                                        nsJSContext::ShrinkingGC);
408       }
409     }
410   } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
411     nsJSContext::SetLowMemoryState(false);
412   } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
413     if (StaticPrefs::javascript_options_compact_on_user_inactive()) {
414       nsJSContext::PokeShrinkingGC();
415     }
416   } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
417     nsJSContext::KillShrinkingGCTimer();
418     if (sIsCompactingOnUserInactive) {
419       AutoJSAPI jsapi;
420       jsapi.Init();
421       JS::AbortIncrementalGC(jsapi.cx());
422     }
423     MOZ_ASSERT(!sIsCompactingOnUserInactive);
424   } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
425              !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
426              !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
427     sShuttingDown = true;
428     KillTimers();
429   }
430 
431   return NS_OK;
432 }
433 
434 /****************************************************************
435  ************************** AutoFree ****************************
436  ****************************************************************/
437 
438 class AutoFree {
439  public:
AutoFree(void * aPtr)440   explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
~AutoFree()441   ~AutoFree() {
442     if (mPtr) free(mPtr);
443   }
Invalidate()444   void Invalidate() { mPtr = 0; }
445 
446  private:
447   void* mPtr;
448 };
449 
450 // A utility function for script languages to call.  Although it looks small,
451 // the use of nsIDocShell and nsPresContext triggers a huge number of
452 // dependencies that most languages would not otherwise need.
453 // XXXmarkh - This function is mis-placed!
NS_HandleScriptError(nsIScriptGlobalObject * aScriptGlobal,const ErrorEventInit & aErrorEventInit,nsEventStatus * aStatus)454 bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
455                           const ErrorEventInit& aErrorEventInit,
456                           nsEventStatus* aStatus) {
457   bool called = false;
458   nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
459   nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
460   if (docShell) {
461     RefPtr<nsPresContext> presContext = docShell->GetPresContext();
462 
463     static int32_t errorDepth;  // Recursion prevention
464     ++errorDepth;
465 
466     if (errorDepth < 2) {
467       // Dispatch() must be synchronous for the recursion block
468       // (errorDepth) to work.
469       RefPtr<ErrorEvent> event =
470           ErrorEvent::Constructor(nsGlobalWindowInner::Cast(win),
471                                   NS_LITERAL_STRING("error"), aErrorEventInit);
472       event->SetTrusted(true);
473 
474       EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
475                                         aStatus);
476       called = true;
477     }
478     --errorDepth;
479   }
480   return called;
481 }
482 
483 class ScriptErrorEvent : public Runnable {
484  public:
ScriptErrorEvent(nsPIDOMWindowInner * aWindow,JS::RootingContext * aRootingCx,xpc::ErrorReport * aReport,JS::Handle<JS::Value> aError,JS::Handle<JSObject * > aErrorStack)485   ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
486                    xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
487                    JS::Handle<JSObject*> aErrorStack)
488       : mozilla::Runnable("ScriptErrorEvent"),
489         mWindow(aWindow),
490         mReport(aReport),
491         mError(aRootingCx, aError),
492         mErrorStack(aRootingCx, aErrorStack) {}
493 
Run()494   NS_IMETHOD Run() override {
495     nsEventStatus status = nsEventStatus_eIgnore;
496     nsPIDOMWindowInner* win = mWindow;
497     MOZ_ASSERT(win);
498     MOZ_ASSERT(NS_IsMainThread());
499     // First, notify the DOM that we have a script error, but only if
500     // our window is still the current inner.
501     JS::RootingContext* rootingCx = RootingCx();
502     if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
503         !sHandlingScriptError) {
504       AutoRestore<bool> recursionGuard(sHandlingScriptError);
505       sHandlingScriptError = true;
506 
507       RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
508 
509       RootedDictionary<ErrorEventInit> init(rootingCx);
510       init.mCancelable = true;
511       init.mFilename = mReport->mFileName;
512       init.mBubbles = true;
513 
514       NS_NAMED_LITERAL_STRING(xoriginMsg, "Script error.");
515       if (!mReport->mIsMuted) {
516         init.mMessage = mReport->mErrorMsg;
517         init.mLineno = mReport->mLineNumber;
518         init.mColno = mReport->mColumn;
519         init.mError = mError;
520       } else {
521         NS_WARNING("Not same origin error!");
522         init.mMessage = xoriginMsg;
523         init.mLineno = 0;
524       }
525 
526       RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
527           nsGlobalWindowInner::Cast(win), NS_LITERAL_STRING("error"), init);
528       event->SetTrusted(true);
529 
530       EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
531                                         &status);
532     }
533 
534     if (status != nsEventStatus_eConsumeNoDefault) {
535       JS::Rooted<JSObject*> stack(rootingCx);
536       JS::Rooted<JSObject*> stackGlobal(rootingCx);
537       xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
538                                               &stackGlobal);
539       JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
540       nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
541       mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
542     }
543 
544     return NS_OK;
545   }
546 
547  private:
548   nsCOMPtr<nsPIDOMWindowInner> mWindow;
549   RefPtr<xpc::ErrorReport> mReport;
550   JS::PersistentRootedValue mError;
551   JS::PersistentRootedObject mErrorStack;
552 
553   static bool sHandlingScriptError;
554 };
555 
556 bool ScriptErrorEvent::sHandlingScriptError = false;
557 
558 // This temporarily lives here to avoid code churn. It will go away entirely
559 // soon.
560 namespace xpc {
561 
DispatchScriptErrorEvent(nsPIDOMWindowInner * win,JS::RootingContext * rootingCx,xpc::ErrorReport * xpcReport,JS::Handle<JS::Value> exception,JS::Handle<JSObject * > exceptionStack)562 void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
563                               JS::RootingContext* rootingCx,
564                               xpc::ErrorReport* xpcReport,
565                               JS::Handle<JS::Value> exception,
566                               JS::Handle<JSObject*> exceptionStack) {
567   nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
568       win, rootingCx, xpcReport, exception, exceptionStack));
569 }
570 
571 } /* namespace xpc */
572 
573 #ifdef DEBUG
574 // A couple of useful functions to call when you're debugging.
JSObject2Win(JSObject * obj)575 nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
576   return xpc::WindowOrNull(obj);
577 }
578 
579 template <typename T>
PrintWinURI(T * win)580 void PrintWinURI(T* win) {
581   if (!win) {
582     printf("No window passed in.\n");
583     return;
584   }
585 
586   nsCOMPtr<Document> doc = win->GetExtantDoc();
587   if (!doc) {
588     printf("No document in the window.\n");
589     return;
590   }
591 
592   nsIURI* uri = doc->GetDocumentURI();
593   if (!uri) {
594     printf("Document doesn't have a URI.\n");
595     return;
596   }
597 
598   printf("%s\n", uri->GetSpecOrDefault().get());
599 }
600 
PrintWinURIInner(nsGlobalWindowInner * aWin)601 void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
602 
PrintWinURIOuter(nsGlobalWindowOuter * aWin)603 void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
604 
605 template <typename T>
PrintWinCodebase(T * win)606 void PrintWinCodebase(T* win) {
607   if (!win) {
608     printf("No window passed in.\n");
609     return;
610   }
611 
612   nsIPrincipal* prin = win->GetPrincipal();
613   if (!prin) {
614     printf("Window doesn't have principals.\n");
615     return;
616   }
617   if (prin->IsSystemPrincipal()) {
618     printf("No URI, it's the system principal.\n");
619     return;
620   }
621   nsCString spec;
622   prin->GetAsciiSpec(spec);
623   printf("%s\n", spec.get());
624 }
625 
PrintWinCodebaseInner(nsGlobalWindowInner * aWin)626 void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
627   return PrintWinCodebase(aWin);
628 }
629 
PrintWinCodebaseOuter(nsGlobalWindowOuter * aWin)630 void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
631   return PrintWinCodebase(aWin);
632 }
633 
DumpString(const nsAString & str)634 void DumpString(const nsAString& str) {
635   printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
636 }
637 #endif
638 
nsJSContext(bool aGCOnDestruction,nsIScriptGlobalObject * aGlobalObject)639 nsJSContext::nsJSContext(bool aGCOnDestruction,
640                          nsIScriptGlobalObject* aGlobalObject)
641     : mWindowProxy(nullptr),
642       mGCOnDestruction(aGCOnDestruction),
643       mGlobalObjectRef(aGlobalObject) {
644   EnsureStatics();
645 
646   mProcessingScriptTag = false;
647   HoldJSObjects(this);
648 }
649 
~nsJSContext()650 nsJSContext::~nsJSContext() {
651   mGlobalObjectRef = nullptr;
652 
653   Destroy();
654 }
655 
Destroy()656 void nsJSContext::Destroy() {
657   if (mGCOnDestruction) {
658     PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
659   }
660 
661   DropJSObjects(this);
662 }
663 
664 // QueryInterface implementation for nsJSContext
665 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
666 
667 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
668   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
669 NS_IMPL_CYCLE_COLLECTION_TRACE_END
670 
671 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
672   tmp->mGCOnDestruction = false;
673   tmp->mWindowProxy = nullptr;
674   tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)675   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
676 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
677 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
678   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
679 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
680 
681 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
682   NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
683   NS_INTERFACE_MAP_ENTRY(nsISupports)
684 NS_INTERFACE_MAP_END
685 
686 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
687 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
688 
689 #ifdef DEBUG
690 bool AtomIsEventHandlerName(nsAtom* aName) {
691   const char16_t* name = aName->GetUTF16String();
692 
693   const char16_t* cp;
694   char16_t c;
695   for (cp = name; *cp != '\0'; ++cp) {
696     c = *cp;
697     if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
698   }
699 
700   return true;
701 }
702 #endif
703 
GetGlobalObject()704 nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
705   // Note: this could probably be simplified somewhat more; see bug 974327
706   // comments 1 and 3.
707   if (!mWindowProxy) {
708     return nullptr;
709   }
710 
711   MOZ_ASSERT(mGlobalObjectRef);
712   return mGlobalObjectRef;
713 }
714 
SetProperty(JS::Handle<JSObject * > aTarget,const char * aPropName,nsISupports * aArgs)715 nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
716                                   const char* aPropName, nsISupports* aArgs) {
717   AutoJSAPI jsapi;
718   if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
719     return NS_ERROR_FAILURE;
720   }
721   JSContext* cx = jsapi.cx();
722 
723   JS::RootedVector<JS::Value> args(cx);
724 
725   JS::Rooted<JSObject*> global(cx, GetWindowProxy());
726   nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
727   NS_ENSURE_SUCCESS(rv, rv);
728 
729   // got the arguments, now attach them.
730 
731   for (uint32_t i = 0; i < args.length(); ++i) {
732     if (!JS_WrapValue(cx, args[i])) {
733       return NS_ERROR_FAILURE;
734     }
735   }
736 
737   JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
738   if (!array) {
739     return NS_ERROR_FAILURE;
740   }
741 
742   return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
743                                                              : NS_ERROR_FAILURE;
744 }
745 
ConvertSupportsTojsvals(JSContext * aCx,nsISupports * aArgs,JS::Handle<JSObject * > aScope,JS::MutableHandleVector<JS::Value> aArgsOut)746 nsresult nsJSContext::ConvertSupportsTojsvals(
747     JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
748     JS::MutableHandleVector<JS::Value> aArgsOut) {
749   nsresult rv = NS_OK;
750 
751   // If the array implements nsIJSArgArray, copy the contents and return.
752   nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
753   if (fastArray) {
754     uint32_t argc;
755     JS::Value* argv;
756     rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
757     if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
758       rv = NS_ERROR_OUT_OF_MEMORY;
759     }
760     return rv;
761   }
762 
763   // Take the slower path converting each item.
764   // Handle only nsIArray and nsIVariant.  nsIArray is only needed for
765   // SetProperty('arguments', ...);
766 
767   nsIXPConnect* xpc = nsContentUtils::XPConnect();
768   NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
769 
770   if (!aArgs) return NS_OK;
771   uint32_t argCount;
772   // This general purpose function may need to convert an arg array
773   // (window.arguments, event-handler args) and a generic property.
774   nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
775 
776   if (argsArray) {
777     rv = argsArray->GetLength(&argCount);
778     NS_ENSURE_SUCCESS(rv, rv);
779     if (argCount == 0) return NS_OK;
780   } else {
781     argCount = 1;  // the nsISupports which is not an array
782   }
783 
784   // Use the caller's auto guards to release and unroot.
785   if (!aArgsOut.resize(argCount)) {
786     return NS_ERROR_OUT_OF_MEMORY;
787   }
788 
789   if (argsArray) {
790     for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
791       nsCOMPtr<nsISupports> arg;
792       JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
793       argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
794                                 getter_AddRefs(arg));
795       if (!arg) {
796         thisVal.setNull();
797         continue;
798       }
799       nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
800       if (variant != nullptr) {
801         rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
802       } else {
803         // And finally, support the nsISupportsPrimitives supplied
804         // by the AppShell.  It generally will pass only strings, but
805         // as we have code for handling all, we may as well use it.
806         rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
807         if (rv == NS_ERROR_NO_INTERFACE) {
808           // something else - probably an event object or similar -
809           // just wrap it.
810 #ifdef DEBUG
811           // but first, check its not another nsISupportsPrimitive, as
812           // these are now deprecated for use with script contexts.
813           nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
814           NS_ASSERTION(prim == nullptr,
815                        "Don't pass nsISupportsPrimitives - use nsIVariant!");
816 #endif
817           JSAutoRealm ar(aCx, aScope);
818           rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
819         }
820       }
821     }
822   } else {
823     nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
824     if (variant) {
825       rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
826     } else {
827       NS_ERROR("Not an array, not an interface?");
828       rv = NS_ERROR_UNEXPECTED;
829     }
830   }
831   return rv;
832 }
833 
834 // This really should go into xpconnect somewhere...
AddSupportsPrimitiveTojsvals(JSContext * aCx,nsISupports * aArg,JS::Value * aArgv)835 nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
836                                                    nsISupports* aArg,
837                                                    JS::Value* aArgv) {
838   MOZ_ASSERT(aArg, "Empty arg");
839 
840   nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
841   if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
842 
843   uint16_t type;
844   argPrimitive->GetType(&type);
845 
846   switch (type) {
847     case nsISupportsPrimitive::TYPE_CSTRING: {
848       nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
849       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
850 
851       nsAutoCString data;
852 
853       p->GetData(data);
854 
855       JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
856       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
857 
858       aArgv->setString(str);
859 
860       break;
861     }
862     case nsISupportsPrimitive::TYPE_STRING: {
863       nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
864       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
865 
866       nsAutoString data;
867 
868       p->GetData(data);
869 
870       // cast is probably safe since wchar_t and char16_t are expected
871       // to be equivalent; both unsigned 16-bit entities
872       JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
873       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
874 
875       aArgv->setString(str);
876       break;
877     }
878     case nsISupportsPrimitive::TYPE_PRBOOL: {
879       nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
880       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
881 
882       bool data;
883 
884       p->GetData(&data);
885 
886       aArgv->setBoolean(data);
887 
888       break;
889     }
890     case nsISupportsPrimitive::TYPE_PRUINT8: {
891       nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
892       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
893 
894       uint8_t data;
895 
896       p->GetData(&data);
897 
898       aArgv->setInt32(data);
899 
900       break;
901     }
902     case nsISupportsPrimitive::TYPE_PRUINT16: {
903       nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
904       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
905 
906       uint16_t data;
907 
908       p->GetData(&data);
909 
910       aArgv->setInt32(data);
911 
912       break;
913     }
914     case nsISupportsPrimitive::TYPE_PRUINT32: {
915       nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
916       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
917 
918       uint32_t data;
919 
920       p->GetData(&data);
921 
922       aArgv->setInt32(data);
923 
924       break;
925     }
926     case nsISupportsPrimitive::TYPE_CHAR: {
927       nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
928       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
929 
930       char data;
931 
932       p->GetData(&data);
933 
934       JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
935       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
936 
937       aArgv->setString(str);
938 
939       break;
940     }
941     case nsISupportsPrimitive::TYPE_PRINT16: {
942       nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
943       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
944 
945       int16_t data;
946 
947       p->GetData(&data);
948 
949       aArgv->setInt32(data);
950 
951       break;
952     }
953     case nsISupportsPrimitive::TYPE_PRINT32: {
954       nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
955       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
956 
957       int32_t data;
958 
959       p->GetData(&data);
960 
961       aArgv->setInt32(data);
962 
963       break;
964     }
965     case nsISupportsPrimitive::TYPE_FLOAT: {
966       nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
967       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
968 
969       float data;
970 
971       p->GetData(&data);
972 
973       *aArgv = ::JS_NumberValue(data);
974 
975       break;
976     }
977     case nsISupportsPrimitive::TYPE_DOUBLE: {
978       nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
979       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
980 
981       double data;
982 
983       p->GetData(&data);
984 
985       *aArgv = ::JS_NumberValue(data);
986 
987       break;
988     }
989     case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
990       nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
991       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
992 
993       nsCOMPtr<nsISupports> data;
994       nsIID* iid = nullptr;
995 
996       p->GetData(getter_AddRefs(data));
997       p->GetDataIID(&iid);
998       NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
999 
1000       AutoFree iidGuard(iid);  // Free iid upon destruction.
1001 
1002       JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
1003       JS::Rooted<JS::Value> v(aCx);
1004       JSAutoRealm ar(aCx, scope);
1005       nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
1006       NS_ENSURE_SUCCESS(rv, rv);
1007 
1008       *aArgv = v;
1009 
1010       break;
1011     }
1012     case nsISupportsPrimitive::TYPE_ID:
1013     case nsISupportsPrimitive::TYPE_PRUINT64:
1014     case nsISupportsPrimitive::TYPE_PRINT64:
1015     case nsISupportsPrimitive::TYPE_PRTIME: {
1016       NS_WARNING("Unsupported primitive type used");
1017       aArgv->setNull();
1018       break;
1019     }
1020     default: {
1021       NS_WARNING("Unknown primitive type used");
1022       aArgv->setNull();
1023       break;
1024     }
1025   }
1026   return NS_OK;
1027 }
1028 
1029 #ifdef MOZ_JPROF
1030 
1031 #  include <signal.h>
1032 
IsJProfAction(struct sigaction * action)1033 inline bool IsJProfAction(struct sigaction* action) {
1034   return (action->sa_sigaction &&
1035           (action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
1036               (SA_RESTART | SA_SIGINFO));
1037 }
1038 
1039 void NS_JProfStartProfiling();
1040 void NS_JProfStopProfiling();
1041 void NS_JProfClearCircular();
1042 
JProfStartProfilingJS(JSContext * cx,unsigned argc,JS::Value * vp)1043 static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
1044   NS_JProfStartProfiling();
1045   return true;
1046 }
1047 
NS_JProfStartProfiling()1048 void NS_JProfStartProfiling() {
1049   // Figure out whether we're dealing with SIGPROF, SIGALRM, or
1050   // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
1051   // JP_RTC_HZ)
1052   struct sigaction action;
1053 
1054   // Must check ALRM before PROF since both are enabled for real-time
1055   sigaction(SIGALRM, nullptr, &action);
1056   // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
1057   if (IsJProfAction(&action)) {
1058     // printf("Beginning real-time jprof profiling.\n");
1059     raise(SIGALRM);
1060     return;
1061   }
1062 
1063   sigaction(SIGPROF, nullptr, &action);
1064   // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
1065   if (IsJProfAction(&action)) {
1066     // printf("Beginning process-time jprof profiling.\n");
1067     raise(SIGPROF);
1068     return;
1069   }
1070 
1071   sigaction(SIGPOLL, nullptr, &action);
1072   // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
1073   if (IsJProfAction(&action)) {
1074     // printf("Beginning rtc-based jprof profiling.\n");
1075     raise(SIGPOLL);
1076     return;
1077   }
1078 
1079   printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
1080 }
1081 
JProfStopProfilingJS(JSContext * cx,unsigned argc,JS::Value * vp)1082 static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
1083   NS_JProfStopProfiling();
1084   return true;
1085 }
1086 
NS_JProfStopProfiling()1087 void NS_JProfStopProfiling() {
1088   raise(SIGUSR1);
1089   // printf("Stopped jprof profiling.\n");
1090 }
1091 
JProfClearCircularJS(JSContext * cx,unsigned argc,JS::Value * vp)1092 static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
1093   NS_JProfClearCircular();
1094   return true;
1095 }
1096 
NS_JProfClearCircular()1097 void NS_JProfClearCircular() {
1098   raise(SIGUSR2);
1099   // printf("cleared jprof buffer\n");
1100 }
1101 
JProfSaveCircularJS(JSContext * cx,unsigned argc,JS::Value * vp)1102 static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
1103   // Not ideal...
1104   NS_JProfStopProfiling();
1105   NS_JProfStartProfiling();
1106   return true;
1107 }
1108 
1109 static const JSFunctionSpec JProfFunctions[] = {
1110     JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
1111     JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
1112     JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
1113     JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
1114 
1115 #endif /* defined(MOZ_JPROF) */
1116 
InitClasses(JS::Handle<JSObject * > aGlobalObj)1117 nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
1118   AutoJSAPI jsapi;
1119   jsapi.Init();
1120   JSContext* cx = jsapi.cx();
1121   JSAutoRealm ar(cx, aGlobalObj);
1122 
1123 #ifdef MOZ_JPROF
1124   // Attempt to initialize JProf functions
1125   ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
1126 #endif
1127 
1128   return NS_OK;
1129 }
1130 
GetProcessingScriptTag()1131 bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
1132 
SetProcessingScriptTag(bool aFlag)1133 void nsJSContext::SetProcessingScriptTag(bool aFlag) {
1134   mProcessingScriptTag = aFlag;
1135 }
1136 
FullGCTimerFired(nsITimer * aTimer,void * aClosure)1137 void FullGCTimerFired(nsITimer* aTimer, void* aClosure) {
1138   nsJSContext::KillFullGCTimer();
1139   MOZ_ASSERT(!aClosure, "Don't pass a closure to FullGCTimerFired");
1140   nsJSContext::GarbageCollectNow(JS::GCReason::FULL_GC_TIMER,
1141                                  nsJSContext::IncrementalGC);
1142 }
1143 
1144 // static
SetLowMemoryState(bool aState)1145 void nsJSContext::SetLowMemoryState(bool aState) {
1146   JSContext* cx = danger::GetJSContext();
1147   JS::SetLowMemoryState(cx, aState);
1148 }
1149 
1150 // static
GarbageCollectNow(JS::GCReason aReason,IsIncremental aIncremental,IsShrinking aShrinking,int64_t aSliceMillis)1151 void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
1152                                     IsIncremental aIncremental,
1153                                     IsShrinking aShrinking,
1154                                     int64_t aSliceMillis) {
1155   AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
1156       "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
1157 
1158   MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
1159 
1160   KillGCTimer();
1161 
1162   // We use danger::GetJSContext() since AutoJSAPI will assert if the current
1163   // thread's context is null (such as during shutdown).
1164   JSContext* cx = danger::GetJSContext();
1165 
1166   if (!nsContentUtils::XPConnect() || !cx) {
1167     return;
1168   }
1169 
1170   if (sCCLockedOut && aIncremental == IncrementalGC) {
1171     // We're in the middle of incremental GC. Do another slice.
1172     JS::PrepareForIncrementalGC(cx);
1173     JS::IncrementalGCSlice(cx, aReason, aSliceMillis);
1174     return;
1175   }
1176 
1177   JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;
1178 
1179   if (aIncremental == NonIncrementalGC ||
1180       aReason == JS::GCReason::FULL_GC_TIMER) {
1181     sNeedsFullGC = true;
1182   }
1183 
1184   if (sNeedsFullGC) {
1185     JS::PrepareForFullGC(cx);
1186   }
1187 
1188   if (aIncremental == IncrementalGC) {
1189     JS::StartIncrementalGC(cx, gckind, aReason, aSliceMillis);
1190   } else {
1191     JS::NonIncrementalGC(cx, gckind, aReason);
1192   }
1193 }
1194 
FinishAnyIncrementalGC()1195 static void FinishAnyIncrementalGC() {
1196   AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
1197 
1198   if (sCCLockedOut) {
1199     AutoJSAPI jsapi;
1200     jsapi.Init();
1201 
1202     // We're in the middle of an incremental GC, so finish it.
1203     JS::PrepareForIncrementalGC(jsapi.cx());
1204     JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
1205   }
1206 }
1207 
BudgetFromDuration(TimeDuration duration)1208 static inline js::SliceBudget BudgetFromDuration(TimeDuration duration) {
1209   return js::SliceBudget(js::TimeBudget(duration.ToMilliseconds()));
1210 }
1211 
FireForgetSkippable(uint32_t aSuspected,bool aRemoveChildless,TimeStamp aDeadline)1212 static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless,
1213                                 TimeStamp aDeadline) {
1214   AUTO_PROFILER_TRACING_MARKER(
1215       "CC", aDeadline.IsNull() ? "ForgetSkippable" : "IdleForgetSkippable",
1216       GCCC);
1217   TimeStamp startTimeStamp = TimeStamp::Now();
1218 
1219   static uint32_t sForgetSkippableCounter = 0;
1220   static TimeStamp sForgetSkippableFrequencyStartTime;
1221   static TimeStamp sLastForgetSkippableEndTime;
1222   static const TimeDuration minute = TimeDuration::FromSeconds(60.0f);
1223 
1224   if (sForgetSkippableFrequencyStartTime.IsNull()) {
1225     sForgetSkippableFrequencyStartTime = startTimeStamp;
1226   } else if (startTimeStamp - sForgetSkippableFrequencyStartTime > minute) {
1227     TimeStamp startPlusMinute = sForgetSkippableFrequencyStartTime + minute;
1228 
1229     // If we had forget skippables only at the beginning of the interval, we
1230     // still want to use the whole time, minute or more, for frequency
1231     // calculation. sLastForgetSkippableEndTime is needed if forget skippable
1232     // takes enough time to push the interval to be over a minute.
1233     TimeStamp endPoint = startPlusMinute > sLastForgetSkippableEndTime
1234                              ? startPlusMinute
1235                              : sLastForgetSkippableEndTime;
1236 
1237     // Duration in minutes.
1238     double duration =
1239         (endPoint - sForgetSkippableFrequencyStartTime).ToSeconds() / 60;
1240     uint32_t frequencyPerMinute = uint32_t(sForgetSkippableCounter / duration);
1241     Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
1242                           frequencyPerMinute);
1243     sForgetSkippableCounter = 0;
1244     sForgetSkippableFrequencyStartTime = startTimeStamp;
1245   }
1246   ++sForgetSkippableCounter;
1247 
1248   FinishAnyIncrementalGC();
1249   bool earlyForgetSkippable = sCleanupsSinceLastGC < kMajorForgetSkippableCalls;
1250 
1251   TimeDuration budgetTime = aDeadline ? (aDeadline - TimeStamp::Now())
1252                                       : kForgetSkippableSliceDuration;
1253 
1254   js::SliceBudget budget = BudgetFromDuration(budgetTime);
1255   nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
1256                                    earlyForgetSkippable);
1257 
1258   sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
1259   ++sCleanupsSinceLastGC;
1260 
1261   TimeStamp now = TimeStamp::Now();
1262   sLastForgetSkippableEndTime = now;
1263 
1264   TimeDuration duration = now - startTimeStamp;
1265 
1266   uint32_t removedPurples = aSuspected - sPreviousSuspectedCount;
1267   sCCStats.UpdateAfterForgetSkippable(duration, removedPurples);
1268 
1269   if (duration.ToSeconds()) {
1270     TimeDuration idleDuration;
1271     if (!aDeadline.IsNull()) {
1272       if (aDeadline < now) {
1273         // This slice overflowed the idle period.
1274         if (aDeadline > startTimeStamp) {
1275           idleDuration = aDeadline - startTimeStamp;
1276         }
1277       } else {
1278         idleDuration = duration;
1279       }
1280     }
1281 
1282     uint32_t percent =
1283         uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1284     Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
1285   }
1286 }
1287 
1288 MOZ_ALWAYS_INLINE
TimeBetween(TimeStamp start,TimeStamp end)1289 static TimeDuration TimeBetween(TimeStamp start, TimeStamp end) {
1290   MOZ_ASSERT(end >= start);
1291   return end - start;
1292 }
1293 
TimeUntilNow(TimeStamp start)1294 static TimeDuration TimeUntilNow(TimeStamp start) {
1295   if (start.IsNull()) {
1296     return TimeDuration();
1297   }
1298   return TimeBetween(start, TimeStamp::Now());
1299 }
1300 
Init()1301 void CycleCollectorStats::Init() {
1302   Clear();
1303 
1304   char* env = getenv("MOZ_CCTIMER");
1305   if (!env) {
1306     return;
1307   }
1308   if (strcmp(env, "none") == 0) {
1309     mFile = nullptr;
1310   } else if (strcmp(env, "stdout") == 0) {
1311     mFile = stdout;
1312   } else if (strcmp(env, "stderr") == 0) {
1313     mFile = stderr;
1314   } else {
1315     mFile = fopen(env, "a");
1316     if (!mFile) {
1317       MOZ_CRASH("Failed to open MOZ_CCTIMER log file.");
1318     }
1319   }
1320 }
1321 
Clear()1322 void CycleCollectorStats::Clear() {
1323   if (mFile && mFile != stdout && mFile != stderr) {
1324     fclose(mFile);
1325   }
1326   *this = CycleCollectorStats();
1327 }
1328 
FinishCycleCollectionSlice()1329 void CycleCollectorStats::FinishCycleCollectionSlice() {
1330   if (mBeginSliceTime.IsNull()) {
1331     // We already called this method from EndCycleCollectionCallback for this
1332     // slice.
1333     return;
1334   }
1335 
1336   mEndSliceTime = TimeStamp::Now();
1337   TimeDuration duration = mEndSliceTime - mBeginSliceTime;
1338 
1339   if (duration.ToSeconds()) {
1340     TimeDuration idleDuration;
1341     if (!mIdleDeadline.IsNull()) {
1342       if (mIdleDeadline < mEndSliceTime) {
1343         // This slice overflowed the idle period.
1344         idleDuration = mIdleDeadline - mBeginSliceTime;
1345       } else {
1346         idleDuration = duration;
1347       }
1348     }
1349 
1350     uint32_t percent =
1351         uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1352     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE,
1353                           percent);
1354   }
1355 
1356   TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
1357   mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
1358   mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
1359   mTotalSliceTime += sliceTime;
1360   mBeginSliceTime = TimeStamp();
1361 }
1362 
PrepareForCycleCollectionSlice(TimeStamp aDeadline)1363 void CycleCollectorStats::PrepareForCycleCollectionSlice(TimeStamp aDeadline) {
1364   mBeginSliceTime = TimeStamp::Now();
1365   mIdleDeadline = aDeadline;
1366 
1367   // Before we begin the cycle collection, make sure there is no active GC.
1368   if (sCCLockedOut) {
1369     mAnyLockedOut = true;
1370     FinishAnyIncrementalGC();
1371     TimeDuration gcTime = TimeUntilNow(mBeginSliceTime);
1372     mMaxGCDuration = std::max(mMaxGCDuration, gcTime);
1373   }
1374 }
1375 
RunForgetSkippable()1376 void CycleCollectorStats::RunForgetSkippable() {
1377   // Run forgetSkippable synchronously to reduce the size of the CC graph. This
1378   // is particularly useful if we recently finished a GC.
1379   TimeStamp beginForgetSkippable = TimeStamp::Now();
1380   bool ranSyncForgetSkippable = false;
1381   while (sCleanupsSinceLastGC < kMajorForgetSkippableCalls) {
1382     FireForgetSkippable(nsCycleCollector_suspectedCount(), false, TimeStamp());
1383     ranSyncForgetSkippable = true;
1384   }
1385 
1386   if (ranSyncForgetSkippable) {
1387     mMaxSkippableDuration =
1388         std::max(mMaxSkippableDuration, TimeUntilNow(beginForgetSkippable));
1389     mRanSyncForgetSkippable = true;
1390   }
1391 }
1392 
UpdateAfterForgetSkippable(TimeDuration duration,uint32_t aRemovedPurples)1393 void CycleCollectorStats::UpdateAfterForgetSkippable(TimeDuration duration,
1394                                                      uint32_t aRemovedPurples) {
1395   if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) {
1396     mMinForgetSkippableTime = duration;
1397   }
1398   if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) {
1399     mMaxForgetSkippableTime = duration;
1400   }
1401   mTotalForgetSkippableTime += duration;
1402   ++mForgetSkippableBeforeCC;
1403 
1404   mRemovedPurples += aRemovedPurples;
1405 }
1406 
SendTelemetry(TimeDuration aCCNowDuration) const1407 void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration) const {
1408   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut);
1409   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE,
1410                         mRanSyncForgetSkippable);
1411   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL,
1412                         aCCNowDuration.ToMilliseconds());
1413   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE,
1414                         mMaxSliceTime.ToMilliseconds());
1415 
1416   if (!sLastCCEndTime.IsNull()) {
1417     TimeDuration timeBetween = TimeBetween(sLastCCEndTime, mBeginTime);
1418     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN,
1419                           timeBetween.ToSeconds());
1420   }
1421 
1422   Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
1423                         mMaxForgetSkippableTime.ToMilliseconds());
1424 }
1425 
MaybeLogStats(const CycleCollectorResults & aResults,uint32_t aCleanups) const1426 void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
1427                                         uint32_t aCleanups) const {
1428   if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) {
1429     return;
1430   }
1431 
1432   TimeDuration delta = GetCollectionTimeDelta();
1433 
1434   nsCString mergeMsg;
1435   if (aResults.mMergedZones) {
1436     mergeMsg.AssignLiteral(" merged");
1437   }
1438 
1439   nsCString gcMsg;
1440   if (aResults.mForcedGC) {
1441     gcMsg.AssignLiteral(", forced a GC");
1442   }
1443 
1444   const char16_t* kFmt =
1445       u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
1446       u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
1447       u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
1448       u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
1449       u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
1450   nsString msg;
1451   nsTextFormatter::ssprintf(
1452       msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
1453       ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
1454       mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
1455       aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
1456       aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
1457       sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
1458       gcMsg.get(), mForgetSkippableBeforeCC,
1459       mMinForgetSkippableTime.ToMilliseconds(),
1460       mMaxForgetSkippableTime.ToMilliseconds(),
1461       mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1462       mTotalForgetSkippableTime.ToMilliseconds(),
1463       mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples);
1464   if (StaticPrefs::javascript_options_mem_log()) {
1465     nsCOMPtr<nsIConsoleService> cs =
1466         do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1467     if (cs) {
1468       cs->LogStringMessage(msg.get());
1469     }
1470   }
1471   if (mFile) {
1472     fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
1473   }
1474 }
1475 
MaybeNotifyStats(const CycleCollectorResults & aResults,TimeDuration aCCNowDuration,uint32_t aCleanups) const1476 void CycleCollectorStats::MaybeNotifyStats(
1477     const CycleCollectorResults& aResults, TimeDuration aCCNowDuration,
1478     uint32_t aCleanups) const {
1479   if (!StaticPrefs::javascript_options_mem_notify()) {
1480     return;
1481   }
1482 
1483   const char16_t* kJSONFmt =
1484       u"{ \"timestamp\": %llu, "
1485       u"\"duration\": %.f, "
1486       u"\"max_slice_pause\": %.f, "
1487       u"\"total_slice_pause\": %.f, "
1488       u"\"max_finish_gc_duration\": %.f, "
1489       u"\"max_sync_skippable_duration\": %.f, "
1490       u"\"suspected\": %lu, "
1491       u"\"visited\": { "
1492       u"\"RCed\": %lu, "
1493       u"\"GCed\": %lu }, "
1494       u"\"collected\": { "
1495       u"\"RCed\": %lu, "
1496       u"\"GCed\": %lu }, "
1497       u"\"waiting_for_gc\": %lu, "
1498       u"\"zones_waiting_for_gc\": %lu, "
1499       u"\"short_living_objects_waiting_for_gc\": %lu, "
1500       u"\"forced_gc\": %d, "
1501       u"\"forget_skippable\": { "
1502       u"\"times_before_cc\": %lu, "
1503       u"\"min\": %.f, "
1504       u"\"max\": %.f, "
1505       u"\"avg\": %.f, "
1506       u"\"total\": %.f, "
1507       u"\"removed\": %lu } "
1508       u"}";
1509 
1510   nsString json;
1511   nsTextFormatter::ssprintf(
1512       json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
1513       mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
1514       mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
1515       mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
1516       aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
1517       sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
1518       aResults.mForcedGC, mForgetSkippableBeforeCC,
1519       mMinForgetSkippableTime.ToMilliseconds(),
1520       mMaxForgetSkippableTime.ToMilliseconds(),
1521       mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1522       mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
1523   nsCOMPtr<nsIObserverService> observerService =
1524       mozilla::services::GetObserverService();
1525   if (observerService) {
1526     observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
1527                                      json.get());
1528   }
1529 }
1530 
1531 // static
CycleCollectNow(nsICycleCollectorListener * aListener)1532 void nsJSContext::CycleCollectNow(nsICycleCollectorListener* aListener) {
1533   if (!NS_IsMainThread()) {
1534     return;
1535   }
1536 
1537   AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
1538 
1539   sCCStats.PrepareForCycleCollectionSlice(TimeStamp());
1540   nsCycleCollector_collect(aListener);
1541   sCCStats.FinishCycleCollectionSlice();
1542 }
1543 
1544 // static
RunCycleCollectorSlice(TimeStamp aDeadline)1545 void nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline) {
1546   if (!NS_IsMainThread()) {
1547     return;
1548   }
1549 
1550   AUTO_PROFILER_TRACING_MARKER(
1551       "CC", aDeadline.IsNull() ? "CCSlice" : "IdleCCSlice", GCCC);
1552 
1553   AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorSlice", GCCC);
1554 
1555   sCCStats.PrepareForCycleCollectionSlice(aDeadline);
1556 
1557   // Decide how long we want to budget for this slice. By default,
1558   // use an unlimited budget.
1559   js::SliceBudget budget = js::SliceBudget::unlimited();
1560 
1561   if (sIncrementalCC) {
1562     TimeDuration baseBudget = kICCSliceBudget;
1563     if (!aDeadline.IsNull()) {
1564       baseBudget = aDeadline - TimeStamp::Now();
1565     }
1566 
1567     if (sCCStats.mBeginTime.IsNull()) {
1568       // If no CC is in progress, use the standard slice time.
1569       budget = BudgetFromDuration(baseBudget);
1570     } else {
1571       TimeStamp now = TimeStamp::Now();
1572 
1573       // Only run a limited slice if we're within the max running time.
1574       TimeDuration runningTime = TimeBetween(sCCStats.mBeginTime, now);
1575       if (runningTime < kMaxICCDuration) {
1576         const TimeDuration maxSlice = TimeDuration::FromMilliseconds(
1577             MainThreadIdlePeriod::GetLongIdlePeriod());
1578 
1579         // Try to make up for a delay in running this slice.
1580         double sliceDelayMultiplier =
1581             TimeBetween(sCCStats.mEndSliceTime, now) / kICCIntersliceDelay;
1582         TimeDuration delaySliceBudget =
1583             std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
1584 
1585         // Increase slice budgets up to |maxSlice| as we approach
1586         // half way through the ICC, to avoid large sync CCs.
1587         double percentToHalfDone =
1588             std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
1589         TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
1590 
1591         budget = BudgetFromDuration(
1592             std::max({delaySliceBudget, laterSliceBudget, baseBudget}));
1593       }
1594     }
1595   }
1596 
1597   nsCycleCollector_collectSlice(
1598       budget,
1599       aDeadline.IsNull() || (aDeadline - TimeStamp::Now()) < kICCSliceBudget);
1600 
1601   sCCStats.FinishCycleCollectionSlice();
1602 }
1603 
1604 // static
RunCycleCollectorWorkSlice(int64_t aWorkBudget)1605 void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
1606   if (!NS_IsMainThread()) {
1607     return;
1608   }
1609 
1610   AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
1611 
1612   sCCStats.PrepareForCycleCollectionSlice();
1613 
1614   js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
1615   nsCycleCollector_collectSlice(budget);
1616 
1617   sCCStats.FinishCycleCollectionSlice();
1618 }
1619 
ClearMaxCCSliceTime()1620 void nsJSContext::ClearMaxCCSliceTime() {
1621   sCCStats.mMaxSliceTimeSinceClear = TimeDuration();
1622 }
1623 
GetMaxCCSliceTimeSinceClear()1624 uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
1625   return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
1626 }
1627 
ICCRunnerFired(TimeStamp aDeadline)1628 static bool ICCRunnerFired(TimeStamp aDeadline) {
1629   if (sDidShutdown) {
1630     return false;
1631   }
1632 
1633   // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
1634   // to synchronously finish the GC, which is bad.
1635 
1636   if (sCCLockedOut) {
1637     TimeStamp now = TimeStamp::Now();
1638     if (!sCCLockedOutTime) {
1639       sCCLockedOutTime = now;
1640       return false;
1641     }
1642     if (now - sCCLockedOutTime < kMaxCCLockedoutTime) {
1643       return false;
1644     }
1645   }
1646 
1647   nsJSContext::RunCycleCollectorSlice(aDeadline);
1648   return true;
1649 }
1650 
1651 // static
BeginCycleCollectionCallback()1652 void nsJSContext::BeginCycleCollectionCallback() {
1653   MOZ_ASSERT(NS_IsMainThread());
1654 
1655   sCCStats.mBeginTime = sCCStats.mBeginSliceTime.IsNull()
1656                             ? TimeStamp::Now()
1657                             : sCCStats.mBeginSliceTime;
1658   sCCStats.mSuspected = nsCycleCollector_suspectedCount();
1659 
1660   KillCCRunner();
1661 
1662   sCCStats.RunForgetSkippable();
1663 
1664   MOZ_ASSERT(!sICCRunner,
1665              "Tried to create a new ICC timer when one already existed.");
1666 
1667   if (sShuttingDown) {
1668     return;
1669   }
1670 
1671   // Create an ICC timer even if ICC is globally disabled, because we could be
1672   // manually triggering an incremental collection, and we want to be sure to
1673   // finish it.
1674   sICCRunner = IdleTaskRunner::Create(
1675       ICCRunnerFired, "BeginCycleCollectionCallback::ICCRunnerFired",
1676       kICCIntersliceDelay.ToMilliseconds(),
1677       kIdleICCSliceBudget.ToMilliseconds(), true, [] { return sShuttingDown; });
1678 }
1679 
1680 // static
EndCycleCollectionCallback(CycleCollectorResults & aResults)1681 void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
1682   MOZ_ASSERT(NS_IsMainThread());
1683 
1684   nsJSContext::KillICCRunner();
1685 
1686   // Update timing information for the current slice before we log it, if
1687   // we previously called PrepareForCycleCollectionSlice(). During shutdown
1688   // CCs, this won't happen.
1689   sCCStats.FinishCycleCollectionSlice();
1690 
1691   sCCollectedWaitingForGC += aResults.mFreedGCed;
1692   sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
1693 
1694   TimeStamp endCCTimeStamp = TimeStamp::Now();
1695   TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
1696 
1697   if (NeedsGCAfterCC()) {
1698     MOZ_ASSERT(StaticPrefs::javascript_options_gc_delay() >
1699                    kMaxICCDuration.ToMilliseconds(),
1700                "A max duration ICC shouldn't reduce GC delay to 0");
1701 
1702     PokeGC(JS::GCReason::CC_WAITING, nullptr,
1703            StaticPrefs::javascript_options_gc_delay() -
1704                std::min(ccNowDuration, kMaxICCDuration).ToMilliseconds());
1705   }
1706 
1707   // Log information about the CC via telemetry, JSON and the console.
1708 
1709   sCCStats.SendTelemetry(ccNowDuration);
1710 
1711   uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u);
1712 
1713   sCCStats.MaybeLogStats(aResults, cleanups);
1714 
1715   sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups);
1716 
1717   // Update global state to indicate we have just run a cycle collection.
1718   sLastCCEndTime = endCCTimeStamp;
1719   sNeedsFullCC = false;
1720   sNeedsGCAfterCC = false;
1721   sCCStats.Clear();
1722 }
1723 
1724 // static
InterSliceGCRunnerFired(TimeStamp aDeadline,void * aData)1725 bool InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData) {
1726   MOZ_ASSERT(sActiveIntersliceGCBudget);
1727   // We use longer budgets when the CC has been locked out but the CC has tried
1728   // to run since that means we may have significant amount garbage to collect
1729   // and better to GC in several longer slices than in a very long one.
1730   TimeDuration budget = aDeadline.IsNull() ? sActiveIntersliceGCBudget * 2
1731                                            : aDeadline - TimeStamp::Now();
1732   if (sCCLockedOut && sCCLockedOutTime) {
1733     TimeDuration lockedTime = TimeStamp::Now() - sCCLockedOutTime;
1734     TimeDuration maxSliceGCBudget = sActiveIntersliceGCBudget * 10;
1735     double percentOfLockedTime =
1736         std::min(lockedTime / kMaxCCLockedoutTime, 1.0);
1737     budget = std::max(budget, maxSliceGCBudget.MultDouble(percentOfLockedTime));
1738   }
1739 
1740   TimeStamp startTimeStamp = TimeStamp::Now();
1741   TimeDuration duration = sGCUnnotifiedTotalTime;
1742   uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
1743   nsJSContext::GarbageCollectNow(
1744       aData ? static_cast<JS::GCReason>(reason) : JS::GCReason::INTER_SLICE_GC,
1745       nsJSContext::IncrementalGC, nsJSContext::NonShrinkingGC,
1746       budget.ToMilliseconds());
1747 
1748   sGCUnnotifiedTotalTime = TimeDuration();
1749   TimeStamp now = TimeStamp::Now();
1750   TimeDuration sliceDuration = now - startTimeStamp;
1751   duration += sliceDuration;
1752   if (duration.ToSeconds()) {
1753     TimeDuration idleDuration;
1754     if (!aDeadline.IsNull()) {
1755       if (aDeadline < now) {
1756         // This slice overflowed the idle period.
1757         idleDuration = aDeadline - startTimeStamp;
1758       } else {
1759         // Note, we don't want to use duration here, since it may contain
1760         // data also from JS engine triggered GC slices.
1761         idleDuration = sliceDuration;
1762       }
1763     }
1764 
1765     uint32_t percent =
1766         uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1767     Telemetry::Accumulate(Telemetry::GC_SLICE_DURING_IDLE, percent);
1768   }
1769 
1770   // If the GC doesn't have any more work to do on the foreground thread (and
1771   // e.g. is waiting for background sweeping to finish) then return false to
1772   // make IdleTaskRunner postpone the next call a bit.
1773   JSContext* cx = danger::GetJSContext();
1774   return JS::IncrementalGCHasForegroundWork(cx);
1775 }
1776 
1777 // static
GCTimerFired(nsITimer * aTimer,void * aClosure)1778 void GCTimerFired(nsITimer* aTimer, void* aClosure) {
1779   nsJSContext::KillGCTimer();
1780   if (sShuttingDown) {
1781     nsJSContext::KillInterSliceGCRunner();
1782     return;
1783   }
1784 
1785   if (sInterSliceGCRunner) {
1786     return;
1787   }
1788 
1789   // Now start the actual GC after initial timer has fired.
1790   sInterSliceGCRunner = IdleTaskRunner::Create(
1791       [aClosure](TimeStamp aDeadline) {
1792         return InterSliceGCRunnerFired(aDeadline, aClosure);
1793       },
1794       "GCTimerFired::InterSliceGCRunnerFired",
1795       StaticPrefs::javascript_options_gc_delay_interslice(),
1796       sActiveIntersliceGCBudget.ToMilliseconds(), true,
1797       [] { return sShuttingDown; });
1798 }
1799 
1800 // static
ShrinkingGCTimerFired(nsITimer * aTimer,void * aClosure)1801 void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
1802   nsJSContext::KillShrinkingGCTimer();
1803   sIsCompactingOnUserInactive = true;
1804   nsJSContext::GarbageCollectNow(JS::GCReason::USER_INACTIVE,
1805                                  nsJSContext::IncrementalGC,
1806                                  nsJSContext::ShrinkingGC);
1807 }
1808 
ShouldTriggerCC(uint32_t aSuspected)1809 static bool ShouldTriggerCC(uint32_t aSuspected) {
1810   return sNeedsFullCC || aSuspected > kCCPurpleLimit ||
1811          (aSuspected > kCCForcedPurpleLimit &&
1812           TimeUntilNow(sLastCCEndTime) > kCCForced);
1813 }
1814 
ShouldFireForgetSkippable(uint32_t aSuspected)1815 static inline bool ShouldFireForgetSkippable(uint32_t aSuspected) {
1816   // Only do a forget skippable if there are more than a few new objects
1817   // or we're doing the initial forget skippables.
1818   return ((sPreviousSuspectedCount + 100) <= aSuspected) ||
1819          sCleanupsSinceLastGC < kMajorForgetSkippableCalls;
1820 }
1821 
IsLastEarlyCCTimer(int32_t aCurrentFireCount)1822 static inline bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
1823   int32_t numEarlyTimerFires =
1824       std::max(int32_t(sCCDelay / kCCSkippableDelay) - 2, 1);
1825 
1826   return aCurrentFireCount >= numEarlyTimerFires;
1827 }
1828 
ActivateCCRunner()1829 static void ActivateCCRunner() {
1830   MOZ_ASSERT(sCCRunnerState == CCRunnerState::Inactive);
1831   sCCRunnerState = CCRunnerState::EarlyTimer;
1832   sCCDelay = kCCDelay;
1833   sCCRunnerEarlyFireCount = 0;
1834 }
1835 
CCRunnerFired(TimeStamp aDeadline)1836 static bool CCRunnerFired(TimeStamp aDeadline) {
1837   if (sDidShutdown) {
1838     return false;
1839   }
1840 
1841   if (sCCLockedOut) {
1842     TimeStamp now = TimeStamp::Now();
1843     if (!sCCLockedOutTime) {
1844       // Reset our state so that we run forgetSkippable often enough before
1845       // CC. Because of reduced sCCDelay forgetSkippable will be called just a
1846       // few times. kMaxCCLockedoutTime limit guarantees that we end up calling
1847       // forgetSkippable and CycleCollectNow eventually.
1848       sCCRunnerState = CCRunnerState::EarlyTimer;
1849       sCCRunnerEarlyFireCount = 0;
1850       sCCDelay = kCCDelay / int64_t(3);
1851       sCCLockedOutTime = now;
1852       return false;
1853     }
1854 
1855     if (now - sCCLockedOutTime < kMaxCCLockedoutTime) {
1856       return false;
1857     }
1858   }
1859 
1860   bool didDoWork = false;
1861   bool finished = false;
1862 
1863   uint32_t suspected = nsCycleCollector_suspectedCount();
1864 
1865   switch (sCCRunnerState) {
1866     case CCRunnerState::EarlyTimer:
1867       ++sCCRunnerEarlyFireCount;
1868       if (IsLastEarlyCCTimer(sCCRunnerEarlyFireCount)) {
1869         sCCRunnerState = CCRunnerState::LateTimer;
1870       }
1871 
1872       if (ShouldFireForgetSkippable(suspected)) {
1873         FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
1874                             aDeadline);
1875         didDoWork = true;
1876         break;
1877       }
1878 
1879       if (aDeadline.IsNull()) {
1880         break;
1881       }
1882 
1883       // If we're called during idle time, try to find some work to do by
1884       // advancing to the next state, effectively bypassing some possible forget
1885       // skippable calls.
1886       MOZ_ASSERT(!didDoWork);
1887 
1888       sCCRunnerState = CCRunnerState::LateTimer;
1889       [[fallthrough]];
1890 
1891     case CCRunnerState::LateTimer:
1892       if (!ShouldTriggerCC(suspected)) {
1893         if (ShouldFireForgetSkippable(suspected)) {
1894           FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
1895                               aDeadline);
1896           didDoWork = true;
1897         }
1898         finished = true;
1899         break;
1900       }
1901 
1902       FireForgetSkippable(suspected, /* aRemoveChildless = */ true, aDeadline);
1903       didDoWork = true;
1904       if (!ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
1905         finished = true;
1906         break;
1907       }
1908 
1909       // Our efforts to avoid a CC have failed, so we return to let the
1910       // timer fire once more to trigger a CC.
1911       sCCRunnerState = CCRunnerState::FinalTimer;
1912 
1913       if (!aDeadline.IsNull() && TimeStamp::Now() < aDeadline) {
1914         // Clear content unbinder before the first CC slice.
1915         Element::ClearContentUnbinder();
1916 
1917         if (TimeStamp::Now() < aDeadline) {
1918           // And trigger deferred deletion too.
1919           nsCycleCollector_doDeferredDeletion();
1920         }
1921       }
1922 
1923       break;
1924 
1925     case CCRunnerState::FinalTimer:
1926       if (!ShouldTriggerCC(suspected)) {
1927         if (ShouldFireForgetSkippable(suspected)) {
1928           FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
1929                               aDeadline);
1930           didDoWork = true;
1931         }
1932         finished = true;
1933         break;
1934       }
1935 
1936       // We are in the final timer fire and still meet the conditions for
1937       // triggering a CC. Let RunCycleCollectorSlice finish the current IGC, if
1938       // any because that will allow us to include the GC time in the CC pause.
1939       nsJSContext::RunCycleCollectorSlice(aDeadline);
1940       didDoWork = true;
1941       finished = true;
1942       break;
1943 
1944     default:
1945       MOZ_CRASH("Unexpected CCRunner state");
1946   }
1947 
1948   if (finished) {
1949     sPreviousSuspectedCount = 0;
1950     nsJSContext::KillCCRunner();
1951     if (!didDoWork) {
1952       sLastForgetSkippableCycleEndTime = TimeStamp::Now();
1953     }
1954   }
1955 
1956   return didDoWork;
1957 }
1958 
1959 // static
CleanupsSinceLastGC()1960 uint32_t nsJSContext::CleanupsSinceLastGC() { return sCleanupsSinceLastGC; }
1961 
1962 // Check all of the various collector timers/runners and see if they are waiting
1963 // to fire. This does not check sFullGCTimer, as that's a more expensive
1964 // collection we run on a long timer.
1965 
1966 // static
RunNextCollectorTimer(JS::GCReason aReason,mozilla::TimeStamp aDeadline)1967 void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
1968                                         mozilla::TimeStamp aDeadline) {
1969   if (sShuttingDown) {
1970     return;
1971   }
1972 
1973   if (sGCTimer) {
1974     if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
1975       // Force full GCs when called from reftests so that we collect dead zones
1976       // that have not been scheduled for collection.
1977       sNeedsFullGC = true;
1978     }
1979     GCTimerFired(nullptr, reinterpret_cast<void*>(aReason));
1980     return;
1981   }
1982 
1983   nsCOMPtr<nsIRunnable> runnable;
1984   if (sInterSliceGCRunner) {
1985     sInterSliceGCRunner->SetDeadline(aDeadline);
1986     runnable = sInterSliceGCRunner;
1987   } else {
1988     // Check the CC timers after the GC timers, because the CC timers won't do
1989     // anything if a GC is in progress.
1990     MOZ_ASSERT(!sCCLockedOut,
1991                "Don't check the CC timers if the CC is locked out.");
1992 
1993     if (sCCRunner) {
1994       MOZ_ASSERT(!sICCRunner,
1995                  "Shouldn't have both sCCRunner and sICCRunner active at the "
1996                  "same time");
1997       sCCRunner->SetDeadline(aDeadline);
1998       runnable = sCCRunner;
1999     } else if (sICCRunner) {
2000       sICCRunner->SetDeadline(aDeadline);
2001       runnable = sICCRunner;
2002     }
2003   }
2004 
2005   if (runnable) {
2006     runnable->Run();
2007   }
2008 }
2009 
2010 // static
MaybeRunNextCollectorSlice(nsIDocShell * aDocShell,JS::GCReason aReason)2011 void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
2012                                              JS::GCReason aReason) {
2013   if (!aDocShell || !XRE_IsContentProcess()) {
2014     return;
2015   }
2016 
2017   BrowsingContext* bc = aDocShell->GetBrowsingContext();
2018   if (!bc) {
2019     return;
2020   }
2021 
2022   BrowsingContext* root = bc->Top();
2023   if (bc == root) {
2024     // We don't want to run collectors when loading the top level page.
2025     return;
2026   }
2027 
2028   nsIDocShell* rootDocShell = root->GetDocShell();
2029   if (!rootDocShell) {
2030     return;
2031   }
2032 
2033   Document* rootDocument = rootDocShell->GetDocument();
2034   if (!rootDocument ||
2035       rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
2036       rootDocument->IsInBackgroundWindow()) {
2037     return;
2038   }
2039 
2040   PresShell* presShell = rootDocument->GetPresShell();
2041   if (!presShell) {
2042     return;
2043   }
2044 
2045   nsViewManager* vm = presShell->GetViewManager();
2046   if (!vm) {
2047     return;
2048   }
2049 
2050   // GetLastUserEventTime returns microseconds.
2051   uint32_t lastEventTime = 0;
2052   vm->GetLastUserEventTime(lastEventTime);
2053   uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
2054   // Only try to trigger collectors more often if user hasn't interacted with
2055   // the page for awhile.
2056   if ((currentTime - lastEventTime) >
2057       (StaticPrefs::dom_events_user_interaction_interval() *
2058        PR_USEC_PER_MSEC)) {
2059     Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
2060     // Try to not delay the next RefreshDriver tick, so give a reasonable
2061     // deadline for collectors.
2062     if (next.isSome()) {
2063       nsJSContext::RunNextCollectorTimer(aReason, next.value());
2064     }
2065   }
2066 }
2067 
2068 // static
PokeGC(JS::GCReason aReason,JSObject * aObj,uint32_t aDelay)2069 void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
2070                          uint32_t aDelay) {
2071   if (sShuttingDown) {
2072     return;
2073   }
2074 
2075   if (aObj) {
2076     JS::Zone* zone = JS::GetObjectZone(aObj);
2077     CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
2078   } else if (aReason != JS::GCReason::CC_WAITING) {
2079     sNeedsFullGC = true;
2080   }
2081 
2082   if (sGCTimer || sInterSliceGCRunner) {
2083     // There's already a timer for GC'ing, just return
2084     return;
2085   }
2086 
2087   if (sCCRunner) {
2088     // Make sure CC is called...
2089     sNeedsFullCC = true;
2090     // and GC after it.
2091     sNeedsGCAfterCC = true;
2092     return;
2093   }
2094 
2095   if (sICCRunner) {
2096     // Make sure GC is called after the current CC completes.
2097     // No need to set sNeedsFullCC because we are currently running a CC.
2098     sNeedsGCAfterCC = true;
2099     return;
2100   }
2101 
2102   static bool first = true;
2103 
2104   NS_NewTimerWithFuncCallback(
2105       &sGCTimer, GCTimerFired, reinterpret_cast<void*>(aReason),
2106       aDelay ? aDelay
2107              : (first ? StaticPrefs::javascript_options_gc_delay_first()
2108                       : StaticPrefs::javascript_options_gc_delay()),
2109       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "GCTimerFired");
2110 
2111   first = false;
2112 }
2113 
2114 // static
PokeShrinkingGC()2115 void nsJSContext::PokeShrinkingGC() {
2116   if (sShrinkingGCTimer || sShuttingDown) {
2117     return;
2118   }
2119 
2120   NS_NewTimerWithFuncCallback(
2121       &sShrinkingGCTimer, ShrinkingGCTimerFired, nullptr,
2122       StaticPrefs::javascript_options_compact_on_user_inactive_delay(),
2123       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "ShrinkingGCTimerFired");
2124 }
2125 
2126 // static
MaybePokeCC()2127 void nsJSContext::MaybePokeCC() {
2128   if (sCCRunner || sICCRunner || !sHasRunGC || sShuttingDown) {
2129     return;
2130   }
2131 
2132   // Don't run consecutive CCs too often.
2133   if (sCleanupsSinceLastGC && !sLastCCEndTime.IsNull()) {
2134     TimeDuration sinceLastCCEnd = TimeUntilNow(sLastCCEndTime);
2135     if (sinceLastCCEnd < kCCDelay) {
2136       return;
2137     }
2138   }
2139 
2140   // If GC hasn't run recently and forget skippable only cycle was run,
2141   // don't start a new cycle too soon.
2142   if ((sCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
2143       !sLastForgetSkippableCycleEndTime.IsNull()) {
2144     TimeDuration sinceLastForgetSkippableCycle =
2145         TimeUntilNow(sLastForgetSkippableCycleEndTime);
2146     if (sinceLastForgetSkippableCycle < kTimeBetweenForgetSkippableCycles) {
2147       return;
2148     }
2149   }
2150 
2151   if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
2152     // We can kill some objects before running forgetSkippable.
2153     nsCycleCollector_dispatchDeferredDeletion();
2154 
2155     ActivateCCRunner();
2156     sCCRunner =
2157         IdleTaskRunner::Create(CCRunnerFired, "MaybePokeCC::CCRunnerFired",
2158                                kCCSkippableDelay.ToMilliseconds(),
2159                                kForgetSkippableSliceDuration.ToMilliseconds(),
2160                                true, [] { return sShuttingDown; });
2161   }
2162 }
2163 
2164 // static
KillGCTimer()2165 void nsJSContext::KillGCTimer() {
2166   if (sGCTimer) {
2167     sGCTimer->Cancel();
2168     NS_RELEASE(sGCTimer);
2169   }
2170 }
2171 
KillFullGCTimer()2172 void nsJSContext::KillFullGCTimer() {
2173   if (sFullGCTimer) {
2174     sFullGCTimer->Cancel();
2175     NS_RELEASE(sFullGCTimer);
2176   }
2177 }
2178 
KillInterSliceGCRunner()2179 void nsJSContext::KillInterSliceGCRunner() {
2180   if (sInterSliceGCRunner) {
2181     sInterSliceGCRunner->Cancel();
2182     sInterSliceGCRunner = nullptr;
2183   }
2184 }
2185 
2186 // static
KillShrinkingGCTimer()2187 void nsJSContext::KillShrinkingGCTimer() {
2188   if (sShrinkingGCTimer) {
2189     sShrinkingGCTimer->Cancel();
2190     NS_RELEASE(sShrinkingGCTimer);
2191   }
2192 }
2193 
2194 // static
KillCCRunner()2195 void nsJSContext::KillCCRunner() {
2196   sCCLockedOutTime = TimeStamp();
2197   sCCRunnerState = CCRunnerState::Inactive;
2198   if (sCCRunner) {
2199     sCCRunner->Cancel();
2200     sCCRunner = nullptr;
2201   }
2202 }
2203 
2204 // static
KillICCRunner()2205 void nsJSContext::KillICCRunner() {
2206   sCCLockedOutTime = TimeStamp();
2207 
2208   if (sICCRunner) {
2209     sICCRunner->Cancel();
2210     sICCRunner = nullptr;
2211   }
2212 }
2213 
2214 class NotifyGCEndRunnable : public Runnable {
2215   nsString mMessage;
2216 
2217  public:
NotifyGCEndRunnable(nsString && aMessage)2218   explicit NotifyGCEndRunnable(nsString&& aMessage)
2219       : mozilla::Runnable("NotifyGCEndRunnable"),
2220         mMessage(std::move(aMessage)) {}
2221 
2222   NS_DECL_NSIRUNNABLE
2223 };
2224 
2225 NS_IMETHODIMP
Run()2226 NotifyGCEndRunnable::Run() {
2227   MOZ_ASSERT(NS_IsMainThread());
2228 
2229   nsCOMPtr<nsIObserverService> observerService =
2230       mozilla::services::GetObserverService();
2231   if (!observerService) {
2232     return NS_OK;
2233   }
2234 
2235   const char16_t oomMsg[3] = {'{', '}', 0};
2236   const char16_t* toSend = mMessage.get() ? mMessage.get() : oomMsg;
2237   observerService->NotifyObservers(nullptr, "garbage-collection-statistics",
2238                                    toSend);
2239 
2240   return NS_OK;
2241 }
2242 
DOMGCSliceCallback(JSContext * aCx,JS::GCProgress aProgress,const JS::GCDescription & aDesc)2243 static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
2244                                const JS::GCDescription& aDesc) {
2245   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
2246 
2247   switch (aProgress) {
2248     case JS::GC_CYCLE_BEGIN: {
2249       // Prevent cycle collections and shrinking during incremental GC.
2250       sCCLockedOut = true;
2251       sCurrentGCStartTime = TimeStamp::Now();
2252       break;
2253     }
2254 
2255     case JS::GC_CYCLE_END: {
2256       TimeDuration delta = GetCollectionTimeDelta();
2257 
2258       if (StaticPrefs::javascript_options_mem_log()) {
2259         nsString gcstats;
2260         gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
2261         nsAutoString prefix;
2262         nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
2263                                   delta.ToSeconds(),
2264                                   ProcessNameForCollectorLog(), getpid());
2265         nsString msg = prefix + gcstats;
2266         nsCOMPtr<nsIConsoleService> cs =
2267             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2268         if (cs) {
2269           cs->LogStringMessage(msg.get());
2270         }
2271       }
2272 
2273       if (!sShuttingDown) {
2274         if (StaticPrefs::javascript_options_mem_notify() ||
2275             Telemetry::CanRecordExtended()) {
2276           nsString json;
2277           json.Adopt(aDesc.formatJSONTelemetry(aCx, PR_Now()));
2278           RefPtr<NotifyGCEndRunnable> notify =
2279               new NotifyGCEndRunnable(std::move(json));
2280           SchedulerGroup::Dispatch(TaskCategory::GarbageCollection,
2281                                    notify.forget());
2282         }
2283       }
2284 
2285       sCCLockedOut = false;
2286       sIsCompactingOnUserInactive = false;
2287 
2288       // May need to kill the inter-slice GC runner
2289       nsJSContext::KillInterSliceGCRunner();
2290 
2291       sCCollectedWaitingForGC = 0;
2292       sCCollectedZonesWaitingForGC = 0;
2293       sLikelyShortLivingObjectsNeedingGC = 0;
2294       sCleanupsSinceLastGC = 0;
2295       sNeedsFullCC = true;
2296       sHasRunGC = true;
2297       nsJSContext::MaybePokeCC();
2298 
2299       if (aDesc.isZone_) {
2300         if (!sFullGCTimer && !sShuttingDown) {
2301           NS_NewTimerWithFuncCallback(
2302               &sFullGCTimer, FullGCTimerFired, nullptr,
2303               StaticPrefs::javascript_options_gc_delay_full(),
2304               nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "FullGCTimerFired");
2305         }
2306       } else {
2307         nsJSContext::KillFullGCTimer();
2308       }
2309 
2310       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
2311         nsCycleCollector_dispatchDeferredDeletion();
2312       }
2313 
2314       if (!aDesc.isZone_) {
2315         sNeedsFullGC = false;
2316       }
2317 
2318       Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
2319                             TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
2320       break;
2321     }
2322 
2323     case JS::GC_SLICE_BEGIN:
2324       break;
2325 
2326     case JS::GC_SLICE_END:
2327       sGCUnnotifiedTotalTime +=
2328           aDesc.lastSliceEnd(aCx) - aDesc.lastSliceStart(aCx);
2329 
2330       if (sShuttingDown || aDesc.isComplete_) {
2331         nsJSContext::KillInterSliceGCRunner();
2332       } else if (!sInterSliceGCRunner) {
2333         // If incremental GC wasn't triggered by GCTimerFired, we may not
2334         // have a runner to ensure all the slices are handled. So, create
2335         // the runner here.
2336         sInterSliceGCRunner = IdleTaskRunner::Create(
2337             [](TimeStamp aDeadline) {
2338               return InterSliceGCRunnerFired(aDeadline, nullptr);
2339             },
2340             "DOMGCSliceCallback::InterSliceGCRunnerFired",
2341             StaticPrefs::javascript_options_gc_delay_interslice(),
2342             sActiveIntersliceGCBudget.ToMilliseconds(), true,
2343             [] { return sShuttingDown; });
2344       }
2345 
2346       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
2347         nsCycleCollector_dispatchDeferredDeletion();
2348       }
2349 
2350       if (StaticPrefs::javascript_options_mem_log()) {
2351         nsString gcstats;
2352         gcstats.Adopt(aDesc.formatSliceMessage(aCx));
2353         nsAutoString prefix;
2354         nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
2355                                   ProcessNameForCollectorLog(), getpid());
2356         nsString msg = prefix + gcstats;
2357         nsCOMPtr<nsIConsoleService> cs =
2358             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2359         if (cs) {
2360           cs->LogStringMessage(msg.get());
2361         }
2362       }
2363 
2364       break;
2365 
2366     default:
2367       MOZ_CRASH("Unexpected GCProgress value");
2368   }
2369 
2370   if (sPrevGCSliceCallback) {
2371     (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
2372   }
2373 }
2374 
SetWindowProxy(JS::Handle<JSObject * > aWindowProxy)2375 void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
2376   mWindowProxy = aWindowProxy;
2377 }
2378 
GetWindowProxy()2379 JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
2380 
LikelyShortLivingObjectCreated()2381 void nsJSContext::LikelyShortLivingObjectCreated() {
2382   ++sLikelyShortLivingObjectsNeedingGC;
2383 }
2384 
StartupJSEnvironment()2385 void mozilla::dom::StartupJSEnvironment() {
2386   // initialize all our statics, so that we can restart XPCOM
2387   sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
2388   sCCLockedOut = false;
2389   sCCLockedOutTime = TimeStamp();
2390   sLastCCEndTime = TimeStamp();
2391   sLastForgetSkippableCycleEndTime = TimeStamp();
2392   sHasRunGC = false;
2393   sCCollectedWaitingForGC = 0;
2394   sCCollectedZonesWaitingForGC = 0;
2395   sLikelyShortLivingObjectsNeedingGC = 0;
2396   sNeedsFullCC = false;
2397   sNeedsFullGC = true;
2398   sNeedsGCAfterCC = false;
2399   sIsInitialized = false;
2400   sDidShutdown = false;
2401   sShuttingDown = false;
2402   sCCStats.Init();
2403 }
2404 
SetGCParameter(JSGCParamKey aParam,uint32_t aValue)2405 static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
2406   AutoJSAPI jsapi;
2407   jsapi.Init();
2408   JS_SetGCParameter(jsapi.cx(), aParam, aValue);
2409 }
2410 
ResetGCParameter(JSGCParamKey aParam)2411 static void ResetGCParameter(JSGCParamKey aParam) {
2412   AutoJSAPI jsapi;
2413   jsapi.Init();
2414   JS_ResetGCParameter(jsapi.cx(), aParam);
2415 }
2416 
SetMemoryPrefChangedCallbackMB(const char * aPrefName,void * aClosure)2417 static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
2418                                            void* aClosure) {
2419   int32_t prefMB = Preferences::GetInt(aPrefName, -1);
2420   // handle overflow and negative pref values
2421   CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
2422   if (prefB.isValid() && prefB.value() >= 0) {
2423     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
2424   } else {
2425     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
2426   }
2427 }
2428 
SetMemoryNurseryPrefChangedCallback(const char * aPrefName,void * aClosure)2429 static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
2430                                                 void* aClosure) {
2431   int32_t prefKB = Preferences::GetInt(aPrefName, -1);
2432   // handle overflow and negative pref values
2433   CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
2434   if (prefB.isValid() && prefB.value() >= 0) {
2435     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
2436   } else {
2437     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
2438   }
2439 }
2440 
SetMemoryPrefChangedCallbackInt(const char * aPrefName,void * aClosure)2441 static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
2442                                             void* aClosure) {
2443   int32_t pref = Preferences::GetInt(aPrefName, -1);
2444   // handle overflow and negative pref values
2445   if (pref >= 0 && pref < 10000) {
2446     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
2447   } else {
2448     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
2449   }
2450 }
2451 
SetMemoryPrefChangedCallbackBool(const char * aPrefName,void * aClosure)2452 static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
2453                                              void* aClosure) {
2454   bool pref = Preferences::GetBool(aPrefName);
2455   SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
2456 }
2457 
SetMemoryGCModePrefChangedCallback(const char * aPrefName,void * aClosure)2458 static void SetMemoryGCModePrefChangedCallback(const char* aPrefName,
2459                                                void* aClosure) {
2460   bool enableZoneGC =
2461       Preferences::GetBool("javascript.options.mem.gc_per_zone");
2462   bool enableIncrementalGC =
2463       Preferences::GetBool("javascript.options.mem.gc_incremental");
2464   JSGCMode mode;
2465   if (enableIncrementalGC) {
2466     if (enableZoneGC) {
2467       mode = JSGC_MODE_ZONE_INCREMENTAL;
2468     } else {
2469       mode = JSGC_MODE_INCREMENTAL;
2470     }
2471   } else {
2472     if (enableZoneGC) {
2473       mode = JSGC_MODE_ZONE;
2474     } else {
2475       mode = JSGC_MODE_GLOBAL;
2476     }
2477   }
2478 
2479   SetGCParameter(JSGC_MODE, mode);
2480 }
2481 
SetMemoryGCSliceTimePrefChangedCallback(const char * aPrefName,void * aClosure)2482 static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
2483                                                     void* aClosure) {
2484   int32_t pref = Preferences::GetInt(aPrefName, -1);
2485   // handle overflow and negative pref values
2486   if (pref > 0 && pref < 100000) {
2487     sActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(pref);
2488     SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
2489   } else {
2490     ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
2491   }
2492 }
2493 
SetIncrementalCCPrefChangedCallback(const char * aPrefName,void * aClosure)2494 static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
2495                                                 void* aClosure) {
2496   bool pref = Preferences::GetBool(aPrefName);
2497   sIncrementalCC = pref;
2498 }
2499 
2500 class JSDispatchableRunnable final : public Runnable {
~JSDispatchableRunnable()2501   ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
2502 
2503  public:
JSDispatchableRunnable(JS::Dispatchable * aDispatchable)2504   explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
2505       : mozilla::Runnable("JSDispatchableRunnable"),
2506         mDispatchable(aDispatchable) {
2507     MOZ_ASSERT(mDispatchable);
2508   }
2509 
2510  protected:
Run()2511   NS_IMETHOD Run() override {
2512     MOZ_ASSERT(NS_IsMainThread());
2513 
2514     AutoJSAPI jsapi;
2515     jsapi.Init();
2516 
2517     JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
2518         sShuttingDown ? JS::Dispatchable::ShuttingDown
2519                       : JS::Dispatchable::NotShuttingDown;
2520 
2521     mDispatchable->run(jsapi.cx(), maybeShuttingDown);
2522     mDispatchable = nullptr;  // mDispatchable may delete itself
2523 
2524     return NS_OK;
2525   }
2526 
2527  private:
2528   JS::Dispatchable* mDispatchable;
2529 };
2530 
DispatchToEventLoop(void * closure,JS::Dispatchable * aDispatchable)2531 static bool DispatchToEventLoop(void* closure,
2532                                 JS::Dispatchable* aDispatchable) {
2533   MOZ_ASSERT(!closure);
2534 
2535   // This callback may execute either on the main thread or a random JS-internal
2536   // helper thread. This callback can be called during shutdown so we cannot
2537   // simply NS_DispatchToMainThread. Failure during shutdown is expected and
2538   // properly handled by the JS engine.
2539 
2540   nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
2541   if (!mainTarget) {
2542     return false;
2543   }
2544 
2545   RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
2546   MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
2547   return true;
2548 }
2549 
ConsumeStream(JSContext * aCx,JS::HandleObject aObj,JS::MimeType aMimeType,JS::StreamConsumer * aConsumer)2550 static bool ConsumeStream(JSContext* aCx, JS::HandleObject aObj,
2551                           JS::MimeType aMimeType,
2552                           JS::StreamConsumer* aConsumer) {
2553   return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
2554                                        nullptr);
2555 }
2556 
EnsureStatics()2557 void nsJSContext::EnsureStatics() {
2558   if (sIsInitialized) {
2559     if (!nsContentUtils::XPConnect()) {
2560       MOZ_CRASH();
2561     }
2562     return;
2563   }
2564 
2565   // Let's make sure that our main thread is the same as the xpcom main thread.
2566   MOZ_ASSERT(NS_IsMainThread());
2567 
2568   AutoJSAPI jsapi;
2569   jsapi.Init();
2570 
2571   sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
2572 
2573   JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
2574   JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
2575                                 FetchUtil::ReportJSStreamError);
2576 
2577   // Set these global xpconnect options...
2578   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
2579                                        "javascript.options.mem.max",
2580                                        (void*)JSGC_MAX_BYTES);
2581   Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
2582                                        "javascript.options.mem.nursery.min_kb",
2583                                        (void*)JSGC_MIN_NURSERY_BYTES);
2584   Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
2585                                        "javascript.options.mem.nursery.max_kb",
2586                                        (void*)JSGC_MAX_NURSERY_BYTES);
2587 
2588   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
2589                                        "javascript.options.mem.gc_per_zone");
2590 
2591   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
2592                                        "javascript.options.mem.gc_incremental");
2593 
2594   Preferences::RegisterCallbackAndCall(
2595       SetMemoryGCSliceTimePrefChangedCallback,
2596       "javascript.options.mem.gc_incremental_slice_ms");
2597 
2598   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
2599                                        "javascript.options.mem.gc_compacting",
2600                                        (void*)JSGC_COMPACTING_ENABLED);
2601 
2602   Preferences::RegisterCallbackAndCall(
2603       SetMemoryPrefChangedCallbackBool,
2604       "javascript.options.mem.incremental_weakmap",
2605       (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
2606 
2607   Preferences::RegisterCallbackAndCall(
2608       SetMemoryPrefChangedCallbackInt,
2609       "javascript.options.mem.gc_high_frequency_time_limit_ms",
2610       (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
2611 
2612   Preferences::RegisterCallbackAndCall(
2613       SetMemoryPrefChangedCallbackInt,
2614       "javascript.options.mem.gc_low_frequency_heap_growth",
2615       (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
2616 
2617   Preferences::RegisterCallbackAndCall(
2618       SetMemoryPrefChangedCallbackInt,
2619       "javascript.options.mem.gc_high_frequency_large_heap_growth",
2620       (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
2621 
2622   Preferences::RegisterCallbackAndCall(
2623       SetMemoryPrefChangedCallbackInt,
2624       "javascript.options.mem.gc_high_frequency_small_heap_growth",
2625       (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
2626 
2627   Preferences::RegisterCallbackAndCall(
2628       SetMemoryPrefChangedCallbackInt,
2629       "javascript.options.mem.gc_small_heap_size_max_mb",
2630       (void*)JSGC_SMALL_HEAP_SIZE_MAX);
2631 
2632   Preferences::RegisterCallbackAndCall(
2633       SetMemoryPrefChangedCallbackInt,
2634       "javascript.options.mem.gc_large_heap_size_min_mb",
2635       (void*)JSGC_LARGE_HEAP_SIZE_MIN);
2636 
2637   Preferences::RegisterCallbackAndCall(
2638       SetMemoryPrefChangedCallbackInt,
2639       "javascript.options.mem.gc_allocation_threshold_mb",
2640       (void*)JSGC_ALLOCATION_THRESHOLD);
2641 
2642   Preferences::RegisterCallbackAndCall(
2643       SetMemoryPrefChangedCallbackInt,
2644       "javascript.options.mem.gc_small_heap_incremental_limit",
2645       (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
2646   Preferences::RegisterCallbackAndCall(
2647       SetMemoryPrefChangedCallbackInt,
2648       "javascript.options.mem.gc_large_heap_incremental_limit",
2649       (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
2650 
2651   Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
2652                                        "dom.cycle_collector.incremental");
2653 
2654   Preferences::RegisterCallbackAndCall(
2655       SetMemoryPrefChangedCallbackInt,
2656       "javascript.options.mem.gc_min_empty_chunk_count",
2657       (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
2658 
2659   Preferences::RegisterCallbackAndCall(
2660       SetMemoryPrefChangedCallbackInt,
2661       "javascript.options.mem.gc_max_empty_chunk_count",
2662       (void*)JSGC_MAX_EMPTY_CHUNK_COUNT);
2663 
2664   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2665   if (!obs) {
2666     MOZ_CRASH();
2667   }
2668 
2669   nsIObserver* observer = new nsJSEnvironmentObserver();
2670   obs->AddObserver(observer, "memory-pressure", false);
2671   obs->AddObserver(observer, "user-interaction-inactive", false);
2672   obs->AddObserver(observer, "user-interaction-active", false);
2673   obs->AddObserver(observer, "quit-application", false);
2674   obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
2675   obs->AddObserver(observer, "content-child-will-shutdown", false);
2676 
2677   sIsInitialized = true;
2678 }
2679 
ShutdownJSEnvironment()2680 void mozilla::dom::ShutdownJSEnvironment() {
2681   KillTimers();
2682 
2683   sShuttingDown = true;
2684   sDidShutdown = true;
2685 }
2686 
AsyncErrorReporter(xpc::ErrorReport * aReport)2687 AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
2688     : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
2689 
SerializeStack(JSContext * aCx,JS::Handle<JSObject * > aStack)2690 void AsyncErrorReporter::SerializeStack(JSContext* aCx,
2691                                         JS::Handle<JSObject*> aStack) {
2692   mStackHolder = MakeUnique<SerializedStackHolder>();
2693   mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
2694 }
2695 
SetException(JSContext * aCx,JS::Handle<JS::Value> aException)2696 void AsyncErrorReporter::SetException(JSContext* aCx,
2697                                       JS::Handle<JS::Value> aException) {
2698   MOZ_ASSERT(NS_IsMainThread());
2699   mException.init(aCx, aException);
2700   mHasException = true;
2701 }
2702 
Run()2703 NS_IMETHODIMP AsyncErrorReporter::Run() {
2704   AutoJSAPI jsapi;
2705   DebugOnly<bool> ok = jsapi.Init(xpc::UnprivilegedJunkScope());
2706   MOZ_ASSERT(ok, "Problem with junk scope?");
2707   JSContext* cx = jsapi.cx();
2708   JS::Rooted<JSObject*> stack(cx);
2709   JS::Rooted<JSObject*> stackGlobal(cx);
2710   if (mStackHolder) {
2711     stack = mStackHolder->ReadStack(cx);
2712     if (stack) {
2713       stackGlobal = JS::CurrentGlobalOrNull(cx);
2714     }
2715   }
2716 
2717   JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
2718   if (mHasException) {
2719     MOZ_ASSERT(NS_IsMainThread());
2720     exception = Some(mException);
2721     // Remove our reference to the exception.
2722     mException.setUndefined();
2723     mHasException = false;
2724   }
2725 
2726   mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
2727   return NS_OK;
2728 }
2729 
2730 // A fast-array class for JS.  This class supports both nsIJSScriptArray and
2731 // nsIArray.  If it is JS itself providing and consuming this class, all work
2732 // can be done via nsIJSScriptArray, and avoid the conversion of elements
2733 // to/from nsISupports.
2734 // When consumed by non-JS (eg, another script language), conversion is done
2735 // on-the-fly.
2736 class nsJSArgArray final : public nsIJSArgArray {
2737  public:
2738   nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
2739                nsresult* prv);
2740 
2741   // nsISupports
2742   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
2743   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
2744                                                          nsIJSArgArray)
2745 
2746   // nsIArray
2747   NS_DECL_NSIARRAY
2748 
2749   // nsIJSArgArray
2750   nsresult GetArgs(uint32_t* argc, void** argv) override;
2751 
2752   void ReleaseJSObjects();
2753 
2754  protected:
2755   ~nsJSArgArray();
2756   JSContext* mContext;
2757   JS::Heap<JS::Value>* mArgv;
2758   uint32_t mArgc;
2759 };
2760 
nsJSArgArray(JSContext * aContext,uint32_t argc,const JS::Value * argv,nsresult * prv)2761 nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
2762                            const JS::Value* argv, nsresult* prv)
2763     : mContext(aContext), mArgv(nullptr), mArgc(argc) {
2764   // copy the array - we don't know its lifetime, and ours is tied to xpcom
2765   // refcounting.
2766   if (argc) {
2767     mArgv = new (fallible) JS::Heap<JS::Value>[argc];
2768     if (!mArgv) {
2769       *prv = NS_ERROR_OUT_OF_MEMORY;
2770       return;
2771     }
2772   }
2773 
2774   // Callers are allowed to pass in a null argv even for argc > 0. They can
2775   // then use GetArgs to initialize the values.
2776   if (argv) {
2777     for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
2778   }
2779 
2780   if (argc > 0) {
2781     mozilla::HoldJSObjects(this);
2782   }
2783 
2784   *prv = NS_OK;
2785 }
2786 
~nsJSArgArray()2787 nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
2788 
ReleaseJSObjects()2789 void nsJSArgArray::ReleaseJSObjects() {
2790   if (mArgv) {
2791     delete[] mArgv;
2792   }
2793   if (mArgc > 0) {
2794     mArgc = 0;
2795     mozilla::DropJSObjects(this);
2796   }
2797 }
2798 
2799 // QueryInterface implementation for nsJSArgArray
2800 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(nsJSArgArray)
2801 
2802 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
2803   tmp->ReleaseJSObjects();
2804 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2805 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
2806 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2807 
2808 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
2809   if (tmp->mArgv) {
2810     for (uint32_t i = 0; i < tmp->mArgc; ++i) {
2811       NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
2812     }
2813   }
2814 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2815 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)2816 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
2817   NS_INTERFACE_MAP_ENTRY(nsIArray)
2818   NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
2819   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
2820 NS_INTERFACE_MAP_END
2821 
2822 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
2823 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
2824 
2825 nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
2826   *argv = (void*)mArgv;
2827   *argc = mArgc;
2828   return NS_OK;
2829 }
2830 
2831 // nsIArray impl
GetLength(uint32_t * aLength)2832 NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
2833   *aLength = mArgc;
2834   return NS_OK;
2835 }
2836 
QueryElementAt(uint32_t index,const nsIID & uuid,void ** result)2837 NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
2838                                            void** result) {
2839   *result = nullptr;
2840   if (index >= mArgc) return NS_ERROR_INVALID_ARG;
2841 
2842   if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
2843       uuid.Equals(NS_GET_IID(nsISupports))) {
2844     // Have to copy a Heap into a Rooted to work with it.
2845     JS::Rooted<JS::Value> val(mContext, mArgv[index]);
2846     return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
2847                                                     (nsIVariant**)result);
2848   }
2849   NS_WARNING("nsJSArgArray only handles nsIVariant");
2850   return NS_ERROR_NO_INTERFACE;
2851 }
2852 
IndexOf(uint32_t startIndex,nsISupports * element,uint32_t * _retval)2853 NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
2854                                     uint32_t* _retval) {
2855   return NS_ERROR_NOT_IMPLEMENTED;
2856 }
2857 
ScriptedEnumerate(const nsIID & aElemIID,uint8_t aArgc,nsISimpleEnumerator ** aResult)2858 NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
2859                                               uint8_t aArgc,
2860                                               nsISimpleEnumerator** aResult) {
2861   return NS_ERROR_NOT_IMPLEMENTED;
2862 }
2863 
EnumerateImpl(const nsID & aEntryIID,nsISimpleEnumerator ** _retval)2864 NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
2865                                           nsISimpleEnumerator** _retval) {
2866   return NS_ERROR_NOT_IMPLEMENTED;
2867 }
2868 
2869 // The factory function
NS_CreateJSArgv(JSContext * aContext,uint32_t argc,const JS::Value * argv,nsIJSArgArray ** aArray)2870 nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
2871                          const JS::Value* argv, nsIJSArgArray** aArray) {
2872   nsresult rv;
2873   nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
2874   if (NS_FAILED(rv)) {
2875     return rv;
2876   }
2877   ret.forget(aArray);
2878   return NS_OK;
2879 }
2880