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 "mozilla/HoldDropJSObjects.h"
31 #include "nsIContent.h"
32 #include "nsCycleCollector.h"
33 #include "nsXPCOMCIDInternal.h"
34 #include "nsServiceManagerUtils.h"
35 #include "nsTextFormatter.h"
36 #ifdef XP_WIN
37 #  include <process.h>
38 #  define getpid _getpid
39 #else
40 #  include <unistd.h>  // for getpid()
41 #endif
42 #include "xpcpublic.h"
43 
44 #include "jsapi.h"
45 #include "js/Array.h"               // JS::NewArrayObject
46 #include "js/PropertyAndElement.h"  // JS_DefineProperty
47 #include "js/PropertySpec.h"
48 #include "js/SliceBudget.h"
49 #include "js/Wrapper.h"
50 #include "nsIArray.h"
51 #include "CCGCScheduler.h"
52 #include "WrapperFactory.h"
53 #include "nsGlobalWindow.h"
54 #include "mozilla/AutoRestore.h"
55 #include "mozilla/BasePrincipal.h"
56 #include "mozilla/PresShell.h"
57 #include "mozilla/SchedulerGroup.h"
58 #include "mozilla/StaticPrefs_javascript.h"
59 #include "mozilla/StaticPtr.h"
60 #include "mozilla/dom/BrowsingContext.h"
61 #include "mozilla/dom/DOMException.h"
62 #include "mozilla/dom/DOMExceptionBinding.h"
63 #include "mozilla/dom/Element.h"
64 #include "mozilla/dom/ErrorEvent.h"
65 #include "mozilla/dom/FetchUtil.h"
66 #include "mozilla/dom/ScriptSettings.h"
67 #include "mozilla/dom/SerializedStackHolder.h"
68 #include "mozilla/CycleCollectedJSRuntime.h"
69 #include "nsRefreshDriver.h"
70 #include "nsJSPrincipals.h"
71 #include "AccessCheck.h"
72 #include "mozilla/Logging.h"
73 #include "prthread.h"
74 
75 #include "mozilla/Preferences.h"
76 #include "mozilla/Telemetry.h"
77 #include "mozilla/dom/BindingUtils.h"
78 #include "mozilla/Attributes.h"
79 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
80 #include "mozilla/ContentEvents.h"
81 #include "mozilla/CycleCollectedJSContext.h"
82 #include "nsCycleCollectionNoteRootCallback.h"
83 #include "nsViewManager.h"
84 #include "mozilla/EventStateManager.h"
85 #include "mozilla/ProfilerLabels.h"
86 #include "mozilla/ProfilerMarkers.h"
87 
88 using namespace mozilla;
89 using namespace mozilla::dom;
90 
91 // Thank you Microsoft!
92 #ifdef CompareString
93 #  undef CompareString
94 #endif
95 
96 static JS::GCSliceCallback sPrevGCSliceCallback;
97 
98 static bool sIncrementalCC = false;
99 
100 static bool sIsInitialized;
101 static bool sShuttingDown;
102 
103 static CCGCScheduler sScheduler;
104 
105 struct CycleCollectorStats {
106   constexpr CycleCollectorStats() = default;
107   void Init();
108   void Clear();
109   void AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline,
110                                            TimeStamp aBeginTime,
111                                            TimeStamp aMaybeAfterGCTime);
112   void AfterCycleCollectionSlice();
113   void AfterSyncForgetSkippable(TimeStamp beginTime);
114   void AfterForgetSkippable(TimeDuration duration, uint32_t aRemovedPurples);
115   void AfterCycleCollection();
116 
117   void SendTelemetry(TimeDuration aCCNowDuration) const;
118   void MaybeLogStats(const CycleCollectorResults& aResults,
119                      uint32_t aCleanups) const;
120   void MaybeNotifyStats(const CycleCollectorResults& aResults,
121                         TimeDuration aCCNowDuration, uint32_t aCleanups) const;
122 
123   // Time the current slice began, including any GC finishing.
124   TimeStamp mBeginSliceTime;
125 
126   // Time the previous slice of the current CC ended.
127   TimeStamp mEndSliceTime;
128 
129   // Time the current cycle collection began.
130   TimeStamp mBeginTime;
131 
132   // The longest GC finishing duration for any slice of the current CC.
133   TimeDuration mMaxGCDuration;
134 
135   // True if we ran sync forget skippable in any slice of the current CC.
136   bool mRanSyncForgetSkippable = false;
137 
138   // Number of suspected objects at the start of the current CC.
139   uint32_t mSuspected = 0;
140 
141   // The longest duration spent on sync forget skippable in any slice of the
142   // current CC.
143   TimeDuration mMaxSkippableDuration;
144 
145   // The longest pause of any slice in the current CC.
146   TimeDuration mMaxSliceTime;
147 
148   // The longest slice time since ClearMaxCCSliceTime() was called.
149   TimeDuration mMaxSliceTimeSinceClear;
150 
151   // The total amount of time spent actually running the current CC.
152   TimeDuration mTotalSliceTime;
153 
154   // True if we were locked out by the GC in any slice of the current CC.
155   bool mAnyLockedOut = false;
156 
157   // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
158   FILE* mFile = nullptr;
159 
160   // In case CC slice was triggered during idle time, set to the end of the idle
161   // period.
162   TimeStamp mIdleDeadline;
163 
164   TimeDuration mMinForgetSkippableTime;
165   TimeDuration mMaxForgetSkippableTime;
166   TimeDuration mTotalForgetSkippableTime;
167   uint32_t mForgetSkippableBeforeCC = 0;
168 
169   uint32_t mRemovedPurples = 0;
170 };
171 
172 static CycleCollectorStats sCCStats;
173 
ProcessNameForCollectorLog()174 static const char* ProcessNameForCollectorLog() {
175   return XRE_GetProcessType() == GeckoProcessType_Default ? "default"
176                                                           : "content";
177 }
178 
179 namespace xpc {
180 
181 // This handles JS Exceptions (via ExceptionStackOrNull), DOM and XPC
182 // Exceptions, and arbitrary values that were associated with a stack by the
183 // JS engine when they were thrown, as specified by exceptionStack.
184 //
185 // Note that the returned stackObj and stackGlobal are _not_ wrapped into the
186 // compartment of exceptionValue.
FindExceptionStackForConsoleReport(nsPIDOMWindowInner * win,JS::HandleValue exceptionValue,JS::HandleObject exceptionStack,JS::MutableHandleObject stackObj,JS::MutableHandleObject stackGlobal)187 void FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
188                                         JS::HandleValue exceptionValue,
189                                         JS::HandleObject exceptionStack,
190                                         JS::MutableHandleObject stackObj,
191                                         JS::MutableHandleObject stackGlobal) {
192   stackObj.set(nullptr);
193   stackGlobal.set(nullptr);
194 
195   if (!exceptionValue.isObject()) {
196     // Use the stack provided by the JS engine, if available. This will not be
197     // a wrapper.
198     if (exceptionStack) {
199       stackObj.set(exceptionStack);
200       stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
201     }
202     return;
203   }
204 
205   if (win && win->AsGlobal()->IsDying()) {
206     // Pretend like we have no stack, so we don't end up keeping the global
207     // alive via the stack.
208     return;
209   }
210 
211   JS::RootingContext* rcx = RootingCx();
212   JS::RootedObject exceptionObject(rcx, &exceptionValue.toObject());
213   if (JSObject* excStack = JS::ExceptionStackOrNull(exceptionObject)) {
214     // At this point we know exceptionObject is a possibly-wrapped
215     // js::ErrorObject that has excStack as stack. excStack might also be a CCW,
216     // but excStack must be same-compartment with the unwrapped ErrorObject.
217     // Return the ErrorObject's global as stackGlobal. This matches what we do
218     // in the ErrorObject's |.stack| getter and ensures stackObj and stackGlobal
219     // are same-compartment.
220     JSObject* unwrappedException = js::UncheckedUnwrap(exceptionObject);
221     stackObj.set(excStack);
222     stackGlobal.set(JS::GetNonCCWObjectGlobal(unwrappedException));
223     return;
224   }
225 
226   // It is not a JS Exception, try DOM Exception.
227   RefPtr<Exception> exception;
228   UNWRAP_OBJECT(DOMException, exceptionObject, exception);
229   if (!exception) {
230     // Not a DOM Exception, try XPC Exception.
231     UNWRAP_OBJECT(Exception, exceptionObject, exception);
232     if (!exception) {
233       // As above, use the stack provided by the JS engine, if available.
234       if (exceptionStack) {
235         stackObj.set(exceptionStack);
236         stackGlobal.set(JS::GetNonCCWObjectGlobal(exceptionStack));
237       }
238       return;
239     }
240   }
241 
242   nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
243   if (!stack) {
244     return;
245   }
246   JS::RootedValue value(rcx);
247   stack->GetNativeSavedFrame(&value);
248   if (value.isObject()) {
249     stackObj.set(&value.toObject());
250     MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj));
251     stackGlobal.set(JS::GetNonCCWObjectGlobal(stackObj));
252     return;
253   }
254 }
255 
256 } /* namespace xpc */
257 
GetCollectionTimeDelta()258 static TimeDuration GetCollectionTimeDelta() {
259   static TimeStamp sFirstCollectionTime;
260   TimeStamp now = TimeStamp::Now();
261   if (sFirstCollectionTime) {
262     return now - sFirstCollectionTime;
263   }
264   sFirstCollectionTime = now;
265   return TimeDuration();
266 }
267 
268 class nsJSEnvironmentObserver final : public nsIObserver {
269   ~nsJSEnvironmentObserver() = default;
270 
271  public:
272   NS_DECL_ISUPPORTS
273   NS_DECL_NSIOBSERVER
274 };
275 
NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver,nsIObserver)276 NS_IMPL_ISUPPORTS(nsJSEnvironmentObserver, nsIObserver)
277 
278 NS_IMETHODIMP
279 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
280                                  const char16_t* aData) {
281   if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
282     if (StaticPrefs::javascript_options_gc_on_memory_pressure()) {
283       if (sShuttingDown) {
284         // Don't GC/CC if we're already shutting down.
285         return NS_OK;
286       }
287       nsDependentString data(aData);
288       if (data.EqualsLiteral("low-memory-ongoing")) {
289         // Don't GC/CC if we are in an ongoing low-memory state since its very
290         // slow and it likely won't help us anyway.
291         return NS_OK;
292       }
293       if (data.EqualsLiteral("heap-minimize")) {
294         // heap-minimize notifiers expect this to run synchronously
295         nsJSContext::DoLowMemoryGC();
296         return NS_OK;
297       }
298       if (data.EqualsLiteral("low-memory")) {
299         nsJSContext::SetLowMemoryState(true);
300       }
301       // Asynchronously GC.
302       nsJSContext::LowMemoryGC();
303     }
304   } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) {
305     nsJSContext::SetLowMemoryState(false);
306   } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
307     sScheduler.UserIsInactive();
308   } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
309     sScheduler.UserIsActive();
310   } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
311              !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
312              !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) {
313     sShuttingDown = true;
314     sScheduler.Shutdown();
315   }
316 
317   return NS_OK;
318 }
319 
320 /****************************************************************
321  ************************** AutoFree ****************************
322  ****************************************************************/
323 
324 class AutoFree {
325  public:
AutoFree(void * aPtr)326   explicit AutoFree(void* aPtr) : mPtr(aPtr) {}
~AutoFree()327   ~AutoFree() {
328     if (mPtr) free(mPtr);
329   }
Invalidate()330   void Invalidate() { mPtr = nullptr; }
331 
332  private:
333   void* mPtr;
334 };
335 
336 // A utility function for script languages to call.  Although it looks small,
337 // the use of nsIDocShell and nsPresContext triggers a huge number of
338 // dependencies that most languages would not otherwise need.
339 // XXXmarkh - This function is mis-placed!
NS_HandleScriptError(nsIScriptGlobalObject * aScriptGlobal,const ErrorEventInit & aErrorEventInit,nsEventStatus * aStatus)340 bool NS_HandleScriptError(nsIScriptGlobalObject* aScriptGlobal,
341                           const ErrorEventInit& aErrorEventInit,
342                           nsEventStatus* aStatus) {
343   bool called = false;
344   nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobal));
345   nsIDocShell* docShell = win ? win->GetDocShell() : nullptr;
346   if (docShell) {
347     RefPtr<nsPresContext> presContext = docShell->GetPresContext();
348 
349     static int32_t errorDepth;  // Recursion prevention
350     ++errorDepth;
351 
352     if (errorDepth < 2) {
353       // Dispatch() must be synchronous for the recursion block
354       // (errorDepth) to work.
355       RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
356           nsGlobalWindowInner::Cast(win), u"error"_ns, aErrorEventInit);
357       event->SetTrusted(true);
358 
359       EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
360                                         aStatus);
361       called = true;
362     }
363     --errorDepth;
364   }
365   return called;
366 }
367 
368 class ScriptErrorEvent : public Runnable {
369  public:
ScriptErrorEvent(nsPIDOMWindowInner * aWindow,JS::RootingContext * aRootingCx,xpc::ErrorReport * aReport,JS::Handle<JS::Value> aError,JS::Handle<JSObject * > aErrorStack)370   ScriptErrorEvent(nsPIDOMWindowInner* aWindow, JS::RootingContext* aRootingCx,
371                    xpc::ErrorReport* aReport, JS::Handle<JS::Value> aError,
372                    JS::Handle<JSObject*> aErrorStack)
373       : mozilla::Runnable("ScriptErrorEvent"),
374         mWindow(aWindow),
375         mReport(aReport),
376         mError(aRootingCx, aError),
377         mErrorStack(aRootingCx, aErrorStack) {}
378 
379   // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
Run()380   MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
381     nsEventStatus status = nsEventStatus_eIgnore;
382     nsCOMPtr<nsPIDOMWindowInner> win = mWindow;
383     MOZ_ASSERT(win);
384     MOZ_ASSERT(NS_IsMainThread());
385     // First, notify the DOM that we have a script error, but only if
386     // our window is still the current inner.
387     JS::RootingContext* rootingCx = RootingCx();
388     if (win->IsCurrentInnerWindow() && win->GetDocShell() &&
389         !sHandlingScriptError) {
390       AutoRestore<bool> recursionGuard(sHandlingScriptError);
391       sHandlingScriptError = true;
392 
393       RefPtr<nsPresContext> presContext = win->GetDocShell()->GetPresContext();
394 
395       RootedDictionary<ErrorEventInit> init(rootingCx);
396       init.mCancelable = true;
397       init.mFilename = mReport->mFileName;
398       init.mBubbles = true;
399 
400       constexpr auto xoriginMsg = u"Script error."_ns;
401       if (!mReport->mIsMuted) {
402         init.mMessage = mReport->mErrorMsg;
403         init.mLineno = mReport->mLineNumber;
404         init.mColno = mReport->mColumn;
405         init.mError = mError;
406       } else {
407         NS_WARNING("Not same origin error!");
408         init.mMessage = xoriginMsg;
409         init.mLineno = 0;
410       }
411 
412       RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
413           nsGlobalWindowInner::Cast(win), u"error"_ns, init);
414       event->SetTrusted(true);
415 
416       EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
417                                         &status);
418     }
419 
420     if (status != nsEventStatus_eConsumeNoDefault) {
421       JS::Rooted<JSObject*> stack(rootingCx);
422       JS::Rooted<JSObject*> stackGlobal(rootingCx);
423       xpc::FindExceptionStackForConsoleReport(win, mError, mErrorStack, &stack,
424                                               &stackGlobal);
425       JS::Rooted<Maybe<JS::Value>> exception(rootingCx, Some(mError));
426       nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(win);
427       mReport->LogToConsoleWithStack(inner, exception, stack, stackGlobal);
428     }
429 
430     return NS_OK;
431   }
432 
433  private:
434   nsCOMPtr<nsPIDOMWindowInner> mWindow;
435   RefPtr<xpc::ErrorReport> mReport;
436   JS::PersistentRootedValue mError;
437   JS::PersistentRootedObject mErrorStack;
438 
439   static bool sHandlingScriptError;
440 };
441 
442 bool ScriptErrorEvent::sHandlingScriptError = false;
443 
444 // This temporarily lives here to avoid code churn. It will go away entirely
445 // soon.
446 namespace xpc {
447 
DispatchScriptErrorEvent(nsPIDOMWindowInner * win,JS::RootingContext * rootingCx,xpc::ErrorReport * xpcReport,JS::Handle<JS::Value> exception,JS::Handle<JSObject * > exceptionStack)448 void DispatchScriptErrorEvent(nsPIDOMWindowInner* win,
449                               JS::RootingContext* rootingCx,
450                               xpc::ErrorReport* xpcReport,
451                               JS::Handle<JS::Value> exception,
452                               JS::Handle<JSObject*> exceptionStack) {
453   nsContentUtils::AddScriptRunner(new ScriptErrorEvent(
454       win, rootingCx, xpcReport, exception, exceptionStack));
455 }
456 
457 } /* namespace xpc */
458 
459 #ifdef DEBUG
460 // A couple of useful functions to call when you're debugging.
JSObject2Win(JSObject * obj)461 nsGlobalWindowInner* JSObject2Win(JSObject* obj) {
462   return xpc::WindowOrNull(obj);
463 }
464 
465 template <typename T>
PrintWinURI(T * win)466 void PrintWinURI(T* win) {
467   if (!win) {
468     printf("No window passed in.\n");
469     return;
470   }
471 
472   nsCOMPtr<Document> doc = win->GetExtantDoc();
473   if (!doc) {
474     printf("No document in the window.\n");
475     return;
476   }
477 
478   nsIURI* uri = doc->GetDocumentURI();
479   if (!uri) {
480     printf("Document doesn't have a URI.\n");
481     return;
482   }
483 
484   printf("%s\n", uri->GetSpecOrDefault().get());
485 }
486 
PrintWinURIInner(nsGlobalWindowInner * aWin)487 void PrintWinURIInner(nsGlobalWindowInner* aWin) { return PrintWinURI(aWin); }
488 
PrintWinURIOuter(nsGlobalWindowOuter * aWin)489 void PrintWinURIOuter(nsGlobalWindowOuter* aWin) { return PrintWinURI(aWin); }
490 
491 template <typename T>
PrintWinCodebase(T * win)492 void PrintWinCodebase(T* win) {
493   if (!win) {
494     printf("No window passed in.\n");
495     return;
496   }
497 
498   nsIPrincipal* prin = win->GetPrincipal();
499   if (!prin) {
500     printf("Window doesn't have principals.\n");
501     return;
502   }
503   if (prin->IsSystemPrincipal()) {
504     printf("No URI, it's the system principal.\n");
505     return;
506   }
507   nsCString spec;
508   prin->GetAsciiSpec(spec);
509   printf("%s\n", spec.get());
510 }
511 
PrintWinCodebaseInner(nsGlobalWindowInner * aWin)512 void PrintWinCodebaseInner(nsGlobalWindowInner* aWin) {
513   return PrintWinCodebase(aWin);
514 }
515 
PrintWinCodebaseOuter(nsGlobalWindowOuter * aWin)516 void PrintWinCodebaseOuter(nsGlobalWindowOuter* aWin) {
517   return PrintWinCodebase(aWin);
518 }
519 
DumpString(const nsAString & str)520 void DumpString(const nsAString& str) {
521   printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
522 }
523 #endif
524 
nsJSContext(bool aGCOnDestruction,nsIScriptGlobalObject * aGlobalObject)525 nsJSContext::nsJSContext(bool aGCOnDestruction,
526                          nsIScriptGlobalObject* aGlobalObject)
527     : mWindowProxy(nullptr),
528       mGCOnDestruction(aGCOnDestruction),
529       mGlobalObjectRef(aGlobalObject) {
530   EnsureStatics();
531 
532   mProcessingScriptTag = false;
533   HoldJSObjects(this);
534 }
535 
~nsJSContext()536 nsJSContext::~nsJSContext() {
537   mGlobalObjectRef = nullptr;
538 
539   Destroy();
540 }
541 
Destroy()542 void nsJSContext::Destroy() {
543   if (mGCOnDestruction) {
544     sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy);
545   }
546 
547   DropJSObjects(this);
548 }
549 
550 // QueryInterface implementation for nsJSContext
551 NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSContext)
552 
553 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSContext)
554   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowProxy)
555 NS_IMPL_CYCLE_COLLECTION_TRACE_END
556 
557 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSContext)
558   tmp->mGCOnDestruction = false;
559   tmp->mWindowProxy = nullptr;
560   tmp->Destroy();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)561   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobalObjectRef)
562 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
563 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSContext)
564   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobalObjectRef)
565 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
566 
567 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSContext)
568   NS_INTERFACE_MAP_ENTRY(nsIScriptContext)
569   NS_INTERFACE_MAP_ENTRY(nsISupports)
570 NS_INTERFACE_MAP_END
571 
572 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSContext)
573 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSContext)
574 
575 #ifdef DEBUG
576 bool AtomIsEventHandlerName(nsAtom* aName) {
577   const char16_t* name = aName->GetUTF16String();
578 
579   const char16_t* cp;
580   char16_t c;
581   for (cp = name; *cp != '\0'; ++cp) {
582     c = *cp;
583     if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) return false;
584   }
585 
586   return true;
587 }
588 #endif
589 
GetGlobalObject()590 nsIScriptGlobalObject* nsJSContext::GetGlobalObject() {
591   // Note: this could probably be simplified somewhat more; see bug 974327
592   // comments 1 and 3.
593   if (!mWindowProxy) {
594     return nullptr;
595   }
596 
597   MOZ_ASSERT(mGlobalObjectRef);
598   return mGlobalObjectRef;
599 }
600 
SetProperty(JS::Handle<JSObject * > aTarget,const char * aPropName,nsISupports * aArgs)601 nsresult nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget,
602                                   const char* aPropName, nsISupports* aArgs) {
603   AutoJSAPI jsapi;
604   if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
605     return NS_ERROR_FAILURE;
606   }
607   JSContext* cx = jsapi.cx();
608 
609   JS::RootedVector<JS::Value> args(cx);
610 
611   JS::Rooted<JSObject*> global(cx, GetWindowProxy());
612   nsresult rv = ConvertSupportsTojsvals(cx, aArgs, global, &args);
613   NS_ENSURE_SUCCESS(rv, rv);
614 
615   // got the arguments, now attach them.
616 
617   for (uint32_t i = 0; i < args.length(); ++i) {
618     if (!JS_WrapValue(cx, args[i])) {
619       return NS_ERROR_FAILURE;
620     }
621   }
622 
623   JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, args));
624   if (!array) {
625     return NS_ERROR_FAILURE;
626   }
627 
628   return JS_DefineProperty(cx, aTarget, aPropName, array, 0) ? NS_OK
629                                                              : NS_ERROR_FAILURE;
630 }
631 
ConvertSupportsTojsvals(JSContext * aCx,nsISupports * aArgs,JS::Handle<JSObject * > aScope,JS::MutableHandleVector<JS::Value> aArgsOut)632 nsresult nsJSContext::ConvertSupportsTojsvals(
633     JSContext* aCx, nsISupports* aArgs, JS::Handle<JSObject*> aScope,
634     JS::MutableHandleVector<JS::Value> aArgsOut) {
635   nsresult rv = NS_OK;
636 
637   // If the array implements nsIJSArgArray, copy the contents and return.
638   nsCOMPtr<nsIJSArgArray> fastArray = do_QueryInterface(aArgs);
639   if (fastArray) {
640     uint32_t argc;
641     JS::Value* argv;
642     rv = fastArray->GetArgs(&argc, reinterpret_cast<void**>(&argv));
643     if (NS_SUCCEEDED(rv) && !aArgsOut.append(argv, argc)) {
644       rv = NS_ERROR_OUT_OF_MEMORY;
645     }
646     return rv;
647   }
648 
649   // Take the slower path converting each item.
650   // Handle only nsIArray and nsIVariant.  nsIArray is only needed for
651   // SetProperty('arguments', ...);
652 
653   nsIXPConnect* xpc = nsContentUtils::XPConnect();
654   NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
655 
656   if (!aArgs) return NS_OK;
657   uint32_t argCount;
658   // This general purpose function may need to convert an arg array
659   // (window.arguments, event-handler args) and a generic property.
660   nsCOMPtr<nsIArray> argsArray(do_QueryInterface(aArgs));
661 
662   if (argsArray) {
663     rv = argsArray->GetLength(&argCount);
664     NS_ENSURE_SUCCESS(rv, rv);
665     if (argCount == 0) return NS_OK;
666   } else {
667     argCount = 1;  // the nsISupports which is not an array
668   }
669 
670   // Use the caller's auto guards to release and unroot.
671   if (!aArgsOut.resize(argCount)) {
672     return NS_ERROR_OUT_OF_MEMORY;
673   }
674 
675   if (argsArray) {
676     for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) {
677       nsCOMPtr<nsISupports> arg;
678       JS::MutableHandle<JS::Value> thisVal = aArgsOut[argCtr];
679       argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports),
680                                 getter_AddRefs(arg));
681       if (!arg) {
682         thisVal.setNull();
683         continue;
684       }
685       nsCOMPtr<nsIVariant> variant(do_QueryInterface(arg));
686       if (variant != nullptr) {
687         rv = xpc->VariantToJS(aCx, aScope, variant, thisVal);
688       } else {
689         // And finally, support the nsISupportsPrimitives supplied
690         // by the AppShell.  It generally will pass only strings, but
691         // as we have code for handling all, we may as well use it.
692         rv = AddSupportsPrimitiveTojsvals(aCx, arg, thisVal.address());
693         if (rv == NS_ERROR_NO_INTERFACE) {
694           // something else - probably an event object or similar -
695           // just wrap it.
696 #ifdef DEBUG
697           // but first, check its not another nsISupportsPrimitive, as
698           // these are now deprecated for use with script contexts.
699           nsCOMPtr<nsISupportsPrimitive> prim(do_QueryInterface(arg));
700           NS_ASSERTION(prim == nullptr,
701                        "Don't pass nsISupportsPrimitives - use nsIVariant!");
702 #endif
703           JSAutoRealm ar(aCx, aScope);
704           rv = nsContentUtils::WrapNative(aCx, arg, thisVal);
705         }
706       }
707     }
708   } else {
709     nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
710     if (variant) {
711       rv = xpc->VariantToJS(aCx, aScope, variant, aArgsOut[0]);
712     } else {
713       NS_ERROR("Not an array, not an interface?");
714       rv = NS_ERROR_UNEXPECTED;
715     }
716   }
717   return rv;
718 }
719 
720 // This really should go into xpconnect somewhere...
AddSupportsPrimitiveTojsvals(JSContext * aCx,nsISupports * aArg,JS::Value * aArgv)721 nsresult nsJSContext::AddSupportsPrimitiveTojsvals(JSContext* aCx,
722                                                    nsISupports* aArg,
723                                                    JS::Value* aArgv) {
724   MOZ_ASSERT(aArg, "Empty arg");
725 
726   nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
727   if (!argPrimitive) return NS_ERROR_NO_INTERFACE;
728 
729   uint16_t type;
730   argPrimitive->GetType(&type);
731 
732   switch (type) {
733     case nsISupportsPrimitive::TYPE_CSTRING: {
734       nsCOMPtr<nsISupportsCString> p(do_QueryInterface(argPrimitive));
735       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
736 
737       nsAutoCString data;
738 
739       p->GetData(data);
740 
741       JSString* str = ::JS_NewStringCopyN(aCx, data.get(), data.Length());
742       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
743 
744       aArgv->setString(str);
745 
746       break;
747     }
748     case nsISupportsPrimitive::TYPE_STRING: {
749       nsCOMPtr<nsISupportsString> p(do_QueryInterface(argPrimitive));
750       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
751 
752       nsAutoString data;
753 
754       p->GetData(data);
755 
756       // cast is probably safe since wchar_t and char16_t are expected
757       // to be equivalent; both unsigned 16-bit entities
758       JSString* str = ::JS_NewUCStringCopyN(aCx, data.get(), data.Length());
759       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
760 
761       aArgv->setString(str);
762       break;
763     }
764     case nsISupportsPrimitive::TYPE_PRBOOL: {
765       nsCOMPtr<nsISupportsPRBool> p(do_QueryInterface(argPrimitive));
766       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
767 
768       bool data;
769 
770       p->GetData(&data);
771 
772       aArgv->setBoolean(data);
773 
774       break;
775     }
776     case nsISupportsPrimitive::TYPE_PRUINT8: {
777       nsCOMPtr<nsISupportsPRUint8> p(do_QueryInterface(argPrimitive));
778       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
779 
780       uint8_t data;
781 
782       p->GetData(&data);
783 
784       aArgv->setInt32(data);
785 
786       break;
787     }
788     case nsISupportsPrimitive::TYPE_PRUINT16: {
789       nsCOMPtr<nsISupportsPRUint16> p(do_QueryInterface(argPrimitive));
790       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
791 
792       uint16_t data;
793 
794       p->GetData(&data);
795 
796       aArgv->setInt32(data);
797 
798       break;
799     }
800     case nsISupportsPrimitive::TYPE_PRUINT32: {
801       nsCOMPtr<nsISupportsPRUint32> p(do_QueryInterface(argPrimitive));
802       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
803 
804       uint32_t data;
805 
806       p->GetData(&data);
807 
808       aArgv->setInt32(data);
809 
810       break;
811     }
812     case nsISupportsPrimitive::TYPE_CHAR: {
813       nsCOMPtr<nsISupportsChar> p(do_QueryInterface(argPrimitive));
814       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
815 
816       char data;
817 
818       p->GetData(&data);
819 
820       JSString* str = ::JS_NewStringCopyN(aCx, &data, 1);
821       NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
822 
823       aArgv->setString(str);
824 
825       break;
826     }
827     case nsISupportsPrimitive::TYPE_PRINT16: {
828       nsCOMPtr<nsISupportsPRInt16> p(do_QueryInterface(argPrimitive));
829       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
830 
831       int16_t data;
832 
833       p->GetData(&data);
834 
835       aArgv->setInt32(data);
836 
837       break;
838     }
839     case nsISupportsPrimitive::TYPE_PRINT32: {
840       nsCOMPtr<nsISupportsPRInt32> p(do_QueryInterface(argPrimitive));
841       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
842 
843       int32_t data;
844 
845       p->GetData(&data);
846 
847       aArgv->setInt32(data);
848 
849       break;
850     }
851     case nsISupportsPrimitive::TYPE_FLOAT: {
852       nsCOMPtr<nsISupportsFloat> p(do_QueryInterface(argPrimitive));
853       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
854 
855       float data;
856 
857       p->GetData(&data);
858 
859       *aArgv = ::JS_NumberValue(data);
860 
861       break;
862     }
863     case nsISupportsPrimitive::TYPE_DOUBLE: {
864       nsCOMPtr<nsISupportsDouble> p(do_QueryInterface(argPrimitive));
865       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
866 
867       double data;
868 
869       p->GetData(&data);
870 
871       *aArgv = ::JS_NumberValue(data);
872 
873       break;
874     }
875     case nsISupportsPrimitive::TYPE_INTERFACE_POINTER: {
876       nsCOMPtr<nsISupportsInterfacePointer> p(do_QueryInterface(argPrimitive));
877       NS_ENSURE_TRUE(p, NS_ERROR_UNEXPECTED);
878 
879       nsCOMPtr<nsISupports> data;
880       nsIID* iid = nullptr;
881 
882       p->GetData(getter_AddRefs(data));
883       p->GetDataIID(&iid);
884       NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
885 
886       AutoFree iidGuard(iid);  // Free iid upon destruction.
887 
888       JS::Rooted<JSObject*> scope(aCx, GetWindowProxy());
889       JS::Rooted<JS::Value> v(aCx);
890       JSAutoRealm ar(aCx, scope);
891       nsresult rv = nsContentUtils::WrapNative(aCx, data, iid, &v);
892       NS_ENSURE_SUCCESS(rv, rv);
893 
894       *aArgv = v;
895 
896       break;
897     }
898     case nsISupportsPrimitive::TYPE_ID:
899     case nsISupportsPrimitive::TYPE_PRUINT64:
900     case nsISupportsPrimitive::TYPE_PRINT64:
901     case nsISupportsPrimitive::TYPE_PRTIME: {
902       NS_WARNING("Unsupported primitive type used");
903       aArgv->setNull();
904       break;
905     }
906     default: {
907       NS_WARNING("Unknown primitive type used");
908       aArgv->setNull();
909       break;
910     }
911   }
912   return NS_OK;
913 }
914 
915 #ifdef MOZ_JPROF
916 
917 #  include <signal.h>
918 
IsJProfAction(struct sigaction * action)919 inline bool IsJProfAction(struct sigaction* action) {
920   return (action->sa_sigaction &&
921           (action->sa_flags & (SA_RESTART | SA_SIGINFO)) ==
922               (SA_RESTART | SA_SIGINFO));
923 }
924 
925 void NS_JProfStartProfiling();
926 void NS_JProfStopProfiling();
927 void NS_JProfClearCircular();
928 
JProfStartProfilingJS(JSContext * cx,unsigned argc,JS::Value * vp)929 static bool JProfStartProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
930   NS_JProfStartProfiling();
931   return true;
932 }
933 
NS_JProfStartProfiling()934 void NS_JProfStartProfiling() {
935   // Figure out whether we're dealing with SIGPROF, SIGALRM, or
936   // SIGPOLL profiling (SIGALRM for JP_REALTIME, SIGPOLL for
937   // JP_RTC_HZ)
938   struct sigaction action;
939 
940   // Must check ALRM before PROF since both are enabled for real-time
941   sigaction(SIGALRM, nullptr, &action);
942   // printf("SIGALRM: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
943   if (IsJProfAction(&action)) {
944     // printf("Beginning real-time jprof profiling.\n");
945     raise(SIGALRM);
946     return;
947   }
948 
949   sigaction(SIGPROF, nullptr, &action);
950   // printf("SIGPROF: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
951   if (IsJProfAction(&action)) {
952     // printf("Beginning process-time jprof profiling.\n");
953     raise(SIGPROF);
954     return;
955   }
956 
957   sigaction(SIGPOLL, nullptr, &action);
958   // printf("SIGPOLL: %p, flags = %x\n",action.sa_sigaction,action.sa_flags);
959   if (IsJProfAction(&action)) {
960     // printf("Beginning rtc-based jprof profiling.\n");
961     raise(SIGPOLL);
962     return;
963   }
964 
965   printf("Could not start jprof-profiling since JPROF_FLAGS was not set.\n");
966 }
967 
JProfStopProfilingJS(JSContext * cx,unsigned argc,JS::Value * vp)968 static bool JProfStopProfilingJS(JSContext* cx, unsigned argc, JS::Value* vp) {
969   NS_JProfStopProfiling();
970   return true;
971 }
972 
NS_JProfStopProfiling()973 void NS_JProfStopProfiling() {
974   raise(SIGUSR1);
975   // printf("Stopped jprof profiling.\n");
976 }
977 
JProfClearCircularJS(JSContext * cx,unsigned argc,JS::Value * vp)978 static bool JProfClearCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
979   NS_JProfClearCircular();
980   return true;
981 }
982 
NS_JProfClearCircular()983 void NS_JProfClearCircular() {
984   raise(SIGUSR2);
985   // printf("cleared jprof buffer\n");
986 }
987 
JProfSaveCircularJS(JSContext * cx,unsigned argc,JS::Value * vp)988 static bool JProfSaveCircularJS(JSContext* cx, unsigned argc, JS::Value* vp) {
989   // Not ideal...
990   NS_JProfStopProfiling();
991   NS_JProfStartProfiling();
992   return true;
993 }
994 
995 static const JSFunctionSpec JProfFunctions[] = {
996     JS_FN("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
997     JS_FN("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
998     JS_FN("JProfClearCircular", JProfClearCircularJS, 0, 0),
999     JS_FN("JProfSaveCircular", JProfSaveCircularJS, 0, 0), JS_FS_END};
1000 
1001 #endif /* defined(MOZ_JPROF) */
1002 
InitClasses(JS::Handle<JSObject * > aGlobalObj)1003 nsresult nsJSContext::InitClasses(JS::Handle<JSObject*> aGlobalObj) {
1004   AutoJSAPI jsapi;
1005   jsapi.Init();
1006   JSContext* cx = jsapi.cx();
1007   JSAutoRealm ar(cx, aGlobalObj);
1008 
1009 #ifdef MOZ_JPROF
1010   // Attempt to initialize JProf functions
1011   ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
1012 #endif
1013 
1014   return NS_OK;
1015 }
1016 
GetProcessingScriptTag()1017 bool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; }
1018 
SetProcessingScriptTag(bool aFlag)1019 void nsJSContext::SetProcessingScriptTag(bool aFlag) {
1020   mProcessingScriptTag = aFlag;
1021 }
1022 
1023 // static
SetLowMemoryState(bool aState)1024 void nsJSContext::SetLowMemoryState(bool aState) {
1025   JSContext* cx = danger::GetJSContext();
1026   JS::SetLowMemoryState(cx, aState);
1027 }
1028 
GarbageCollectImpl(JS::GCReason aReason,nsJSContext::IsShrinking aShrinking,const js::SliceBudget & aBudget)1029 static void GarbageCollectImpl(JS::GCReason aReason,
1030                                nsJSContext::IsShrinking aShrinking,
1031                                const js::SliceBudget& aBudget) {
1032   AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
1033       "nsJSContext::GarbageCollectNow", GCCC, JS::ExplainGCReason(aReason));
1034 
1035   bool wantIncremental = !aBudget.isUnlimited();
1036 
1037   // We use danger::GetJSContext() since AutoJSAPI will assert if the current
1038   // thread's context is null (such as during shutdown).
1039   JSContext* cx = danger::GetJSContext();
1040 
1041   if (!nsContentUtils::XPConnect() || !cx) {
1042     return;
1043   }
1044 
1045   if (sScheduler.InIncrementalGC() && wantIncremental) {
1046     // We're in the middle of incremental GC. Do another slice.
1047     JS::PrepareForIncrementalGC(cx);
1048     JS::IncrementalGCSlice(cx, aReason, aBudget);
1049     return;
1050   }
1051 
1052   JS::GCOptions options = aShrinking == nsJSContext::ShrinkingGC
1053                               ? JS::GCOptions::Shrink
1054                               : JS::GCOptions::Normal;
1055 
1056   if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) {
1057     sScheduler.SetNeedsFullGC();
1058   }
1059 
1060   if (sScheduler.NeedsFullGC()) {
1061     JS::PrepareForFullGC(cx);
1062   }
1063 
1064   if (wantIncremental) {
1065     // Incremental GC slices will be triggered by the GC Runner. If one doesn't
1066     // already exist, create it in the GC_SLICE_END callback for the first
1067     // slice being executed here.
1068     JS::StartIncrementalGC(cx, options, aReason, aBudget);
1069   } else {
1070     JS::NonIncrementalGC(cx, options, aReason);
1071   }
1072 }
1073 
1074 // static
GarbageCollectNow(JS::GCReason aReason,IsShrinking aShrinking)1075 void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
1076                                     IsShrinking aShrinking) {
1077   GarbageCollectImpl(aReason, aShrinking, js::SliceBudget::unlimited());
1078 }
1079 
1080 // static
RunIncrementalGCSlice(JS::GCReason aReason,IsShrinking aShrinking,js::SliceBudget & aBudget)1081 void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason,
1082                                         IsShrinking aShrinking,
1083                                         js::SliceBudget& aBudget) {
1084   AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental GC", GCCC);
1085   GarbageCollectImpl(aReason, aShrinking, aBudget);
1086 }
1087 
FinishAnyIncrementalGC()1088 static void FinishAnyIncrementalGC() {
1089   AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC);
1090 
1091   if (sScheduler.InIncrementalGC()) {
1092     AutoJSAPI jsapi;
1093     jsapi.Init();
1094 
1095     // We're in the middle of an incremental GC, so finish it.
1096     JS::PrepareForIncrementalGC(jsapi.cx());
1097     JS::FinishIncrementalGC(jsapi.cx(), JS::GCReason::CC_FORCED);
1098   }
1099 }
1100 
FireForgetSkippable(bool aRemoveChildless,TimeStamp aDeadline)1101 static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) {
1102   AUTO_PROFILER_MARKER_TEXT("ForgetSkippable", GCCC, {},
1103                             aDeadline.IsNull() ? ""_ns : "(idle)"_ns);
1104   TimeStamp startTimeStamp = TimeStamp::Now();
1105   FinishAnyIncrementalGC();
1106 
1107   uint32_t suspectedBefore = nsCycleCollector_suspectedCount();
1108   js::SliceBudget budget =
1109       sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
1110   bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable();
1111   nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
1112                                    earlyForgetSkippable);
1113   TimeStamp now = TimeStamp::Now();
1114   uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete(
1115       now, suspectedBefore, nsCycleCollector_suspectedCount());
1116 
1117   TimeDuration duration = now - startTimeStamp;
1118 
1119   sCCStats.AfterForgetSkippable(duration, removedPurples);
1120 
1121   if (duration.ToSeconds()) {
1122     TimeDuration idleDuration;
1123     if (!aDeadline.IsNull()) {
1124       if (aDeadline < now) {
1125         // This slice overflowed the idle period.
1126         if (aDeadline > startTimeStamp) {
1127           idleDuration = aDeadline - startTimeStamp;
1128         }
1129       } else {
1130         idleDuration = duration;
1131       }
1132     }
1133 
1134     uint32_t percent =
1135         uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1136     Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_DURING_IDLE, percent);
1137   }
1138 }
1139 
1140 MOZ_ALWAYS_INLINE
TimeBetween(TimeStamp aStart,TimeStamp aEnd)1141 static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
1142   MOZ_ASSERT(aEnd >= aStart);
1143   return aEnd - aStart;
1144 }
1145 
TimeUntilNow(TimeStamp start)1146 static TimeDuration TimeUntilNow(TimeStamp start) {
1147   if (start.IsNull()) {
1148     return TimeDuration();
1149   }
1150   return TimeBetween(start, TimeStamp::Now());
1151 }
1152 
Init()1153 void CycleCollectorStats::Init() {
1154   Clear();
1155 
1156   char* env = getenv("MOZ_CCTIMER");
1157   if (!env) {
1158     return;
1159   }
1160   if (strcmp(env, "none") == 0) {
1161     mFile = nullptr;
1162   } else if (strcmp(env, "stdout") == 0) {
1163     mFile = stdout;
1164   } else if (strcmp(env, "stderr") == 0) {
1165     mFile = stderr;
1166   } else {
1167     mFile = fopen(env, "a");
1168     if (!mFile) {
1169       MOZ_CRASH("Failed to open MOZ_CCTIMER log file.");
1170     }
1171   }
1172 }
1173 
Clear()1174 void CycleCollectorStats::Clear() {
1175   if (mFile && mFile != stdout && mFile != stderr) {
1176     fclose(mFile);
1177   }
1178   *this = CycleCollectorStats();
1179 }
1180 
AfterCycleCollectionSlice()1181 void CycleCollectorStats::AfterCycleCollectionSlice() {
1182   if (mBeginSliceTime.IsNull()) {
1183     // We already called this method from EndCycleCollectionCallback for this
1184     // slice.
1185     return;
1186   }
1187 
1188   mEndSliceTime = TimeStamp::Now();
1189   TimeDuration duration = mEndSliceTime - mBeginSliceTime;
1190 
1191   if (duration.ToSeconds()) {
1192     TimeDuration idleDuration;
1193     if (!mIdleDeadline.IsNull()) {
1194       if (mIdleDeadline < mEndSliceTime) {
1195         // This slice overflowed the idle period.
1196         if (mIdleDeadline > mBeginSliceTime) {
1197           idleDuration = mIdleDeadline - mBeginSliceTime;
1198         }
1199       } else {
1200         idleDuration = duration;
1201       }
1202     }
1203 
1204     uint32_t percent =
1205         uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
1206     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SLICE_DURING_IDLE,
1207                           percent);
1208   }
1209 
1210   TimeDuration sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
1211   mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
1212   mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
1213   mTotalSliceTime += sliceTime;
1214   mBeginSliceTime = TimeStamp();
1215 }
1216 
AfterPrepareForCycleCollectionSlice(TimeStamp aDeadline,TimeStamp aBeginTime,TimeStamp aMaybeAfterGCTime)1217 void CycleCollectorStats::AfterPrepareForCycleCollectionSlice(
1218     TimeStamp aDeadline, TimeStamp aBeginTime, TimeStamp aMaybeAfterGCTime) {
1219   mBeginSliceTime = aBeginTime;
1220   mIdleDeadline = aDeadline;
1221 
1222   if (!aMaybeAfterGCTime.IsNull()) {
1223     mAnyLockedOut = true;
1224     mMaxGCDuration = std::max(mMaxGCDuration, aMaybeAfterGCTime - aBeginTime);
1225   }
1226 }
1227 
AfterSyncForgetSkippable(TimeStamp beginTime)1228 void CycleCollectorStats::AfterSyncForgetSkippable(TimeStamp beginTime) {
1229   mMaxSkippableDuration =
1230       std::max(mMaxSkippableDuration, TimeUntilNow(beginTime));
1231   mRanSyncForgetSkippable = true;
1232 }
1233 
AfterForgetSkippable(TimeDuration duration,uint32_t aRemovedPurples)1234 void CycleCollectorStats::AfterForgetSkippable(TimeDuration duration,
1235                                                uint32_t aRemovedPurples) {
1236   if (!mMinForgetSkippableTime || mMinForgetSkippableTime > duration) {
1237     mMinForgetSkippableTime = duration;
1238   }
1239   if (!mMaxForgetSkippableTime || mMaxForgetSkippableTime < duration) {
1240     mMaxForgetSkippableTime = duration;
1241   }
1242   mTotalForgetSkippableTime += duration;
1243   ++mForgetSkippableBeforeCC;
1244 
1245   mRemovedPurples += aRemovedPurples;
1246 }
1247 
SendTelemetry(TimeDuration aCCNowDuration) const1248 void CycleCollectorStats::SendTelemetry(TimeDuration aCCNowDuration) const {
1249   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, mAnyLockedOut);
1250   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE,
1251                         mRanSyncForgetSkippable);
1252   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL,
1253                         aCCNowDuration.ToMilliseconds());
1254   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE,
1255                         mMaxSliceTime.ToMilliseconds());
1256 
1257   TimeStamp lastCCEndTime = sScheduler.GetLastCCEndTime();
1258   if (!lastCCEndTime.IsNull()) {
1259     TimeDuration timeBetween = TimeBetween(lastCCEndTime, mBeginTime);
1260     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN,
1261                           timeBetween.ToSeconds());
1262   }
1263 
1264   Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
1265                         mMaxForgetSkippableTime.ToMilliseconds());
1266 }
1267 
MaybeLogStats(const CycleCollectorResults & aResults,uint32_t aCleanups) const1268 void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
1269                                         uint32_t aCleanups) const {
1270   if (!StaticPrefs::javascript_options_mem_log() && !sCCStats.mFile) {
1271     return;
1272   }
1273 
1274   TimeDuration delta = GetCollectionTimeDelta();
1275 
1276   nsCString mergeMsg;
1277   if (aResults.mMergedZones) {
1278     mergeMsg.AssignLiteral(" merged");
1279   }
1280 
1281   nsCString gcMsg;
1282   if (aResults.mForcedGC) {
1283     gcMsg.AssignLiteral(", forced a GC");
1284   }
1285 
1286   const char16_t* kFmt =
1287       u"CC(T+%.1f)[%s-%i] max pause: %.fms, total time: %.fms, slices: %lu, "
1288       u"suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu "
1289       u"RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
1290       u"ForgetSkippable %lu times before CC, min: %.f ms, max: %.f ms, avg: "
1291       u"%.f ms, total: %.f ms, max sync: %.f ms, removed: %lu";
1292   nsString msg;
1293   nsTextFormatter::ssprintf(
1294       msg, kFmt, delta.ToMicroseconds() / PR_USEC_PER_SEC,
1295       ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
1296       mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
1297       aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
1298       aResults.mFreedRefCounted, aResults.mFreedGCed,
1299       sScheduler.mCCollectedWaitingForGC,
1300       sScheduler.mCCollectedZonesWaitingForGC,
1301       sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
1302       mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
1303       mMaxForgetSkippableTime.ToMilliseconds(),
1304       mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1305       mTotalForgetSkippableTime.ToMilliseconds(),
1306       mMaxSkippableDuration.ToMilliseconds(), mRemovedPurples);
1307   if (StaticPrefs::javascript_options_mem_log()) {
1308     nsCOMPtr<nsIConsoleService> cs =
1309         do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1310     if (cs) {
1311       cs->LogStringMessage(msg.get());
1312     }
1313   }
1314   if (mFile) {
1315     fprintf(mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
1316   }
1317 }
1318 
MaybeNotifyStats(const CycleCollectorResults & aResults,TimeDuration aCCNowDuration,uint32_t aCleanups) const1319 void CycleCollectorStats::MaybeNotifyStats(
1320     const CycleCollectorResults& aResults, TimeDuration aCCNowDuration,
1321     uint32_t aCleanups) const {
1322   if (!StaticPrefs::javascript_options_mem_notify()) {
1323     return;
1324   }
1325 
1326   const char16_t* kJSONFmt =
1327       u"{ \"timestamp\": %llu, "
1328       u"\"duration\": %.f, "
1329       u"\"max_slice_pause\": %.f, "
1330       u"\"total_slice_pause\": %.f, "
1331       u"\"max_finish_gc_duration\": %.f, "
1332       u"\"max_sync_skippable_duration\": %.f, "
1333       u"\"suspected\": %lu, "
1334       u"\"visited\": { "
1335       u"\"RCed\": %lu, "
1336       u"\"GCed\": %lu }, "
1337       u"\"collected\": { "
1338       u"\"RCed\": %lu, "
1339       u"\"GCed\": %lu }, "
1340       u"\"waiting_for_gc\": %lu, "
1341       u"\"zones_waiting_for_gc\": %lu, "
1342       u"\"short_living_objects_waiting_for_gc\": %lu, "
1343       u"\"forced_gc\": %d, "
1344       u"\"forget_skippable\": { "
1345       u"\"times_before_cc\": %lu, "
1346       u"\"min\": %.f, "
1347       u"\"max\": %.f, "
1348       u"\"avg\": %.f, "
1349       u"\"total\": %.f, "
1350       u"\"removed\": %lu } "
1351       u"}";
1352 
1353   nsString json;
1354   nsTextFormatter::ssprintf(
1355       json, kJSONFmt, PR_Now(), aCCNowDuration.ToMilliseconds(),
1356       mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
1357       mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
1358       mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
1359       aResults.mFreedRefCounted, aResults.mFreedGCed,
1360       sScheduler.mCCollectedWaitingForGC,
1361       sScheduler.mCCollectedZonesWaitingForGC,
1362       sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
1363       mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
1364       mMaxForgetSkippableTime.ToMilliseconds(),
1365       mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
1366       mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
1367   nsCOMPtr<nsIObserverService> observerService =
1368       mozilla::services::GetObserverService();
1369   if (observerService) {
1370     observerService->NotifyObservers(nullptr, "cycle-collection-statistics",
1371                                      json.get());
1372   }
1373 }
1374 
1375 // static
CycleCollectNow(CCReason aReason,nsICycleCollectorListener * aListener)1376 void nsJSContext::CycleCollectNow(CCReason aReason,
1377                                   nsICycleCollectorListener* aListener) {
1378   if (!NS_IsMainThread()) {
1379     return;
1380   }
1381 
1382   AUTO_PROFILER_LABEL("nsJSContext::CycleCollectNow", GCCC);
1383 
1384   PrepareForCycleCollectionSlice(aReason, TimeStamp());
1385   nsCycleCollector_collect(aReason, aListener);
1386   sCCStats.AfterCycleCollectionSlice();
1387 }
1388 
1389 // static
PrepareForCycleCollectionSlice(CCReason aReason,TimeStamp aDeadline)1390 void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason,
1391                                                  TimeStamp aDeadline) {
1392   TimeStamp beginTime = TimeStamp::Now();
1393 
1394   // Before we begin the cycle collection, make sure there is no active GC.
1395   TimeStamp afterGCTime;
1396   if (sScheduler.InIncrementalGC()) {
1397     FinishAnyIncrementalGC();
1398     afterGCTime = TimeStamp::Now();
1399   }
1400 
1401   if (!sScheduler.IsCollectingCycles()) {
1402     sScheduler.NoteCCBegin(aReason, beginTime);
1403   }
1404 
1405   sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime,
1406                                                afterGCTime);
1407 }
1408 
1409 // static
RunCycleCollectorSlice(CCReason aReason,TimeStamp aDeadline)1410 void nsJSContext::RunCycleCollectorSlice(CCReason aReason,
1411                                          TimeStamp aDeadline) {
1412   if (!NS_IsMainThread()) {
1413     return;
1414   }
1415 
1416   AUTO_PROFILER_MARKER_TEXT("CCSlice", GCCC, {},
1417                             aDeadline.IsNull() ? ""_ns : "(idle)"_ns);
1418 
1419   PrepareForCycleCollectionSlice(aReason, aDeadline);
1420 
1421   // Decide how long we want to budget for this slice.
1422   if (sIncrementalCC) {
1423     bool preferShorterSlices;
1424     js::SliceBudget budget = sScheduler.ComputeCCSliceBudget(
1425         aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime,
1426         TimeStamp::Now(), &preferShorterSlices);
1427     nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices);
1428   } else {
1429     js::SliceBudget budget = js::SliceBudget::unlimited();
1430     nsCycleCollector_collectSlice(budget, aReason, false);
1431   }
1432 
1433   sCCStats.AfterCycleCollectionSlice();
1434 }
1435 
1436 // static
RunCycleCollectorWorkSlice(int64_t aWorkBudget)1437 void nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget) {
1438   if (!NS_IsMainThread()) {
1439     return;
1440   }
1441 
1442   AUTO_PROFILER_LABEL("nsJSContext::RunCycleCollectorWorkSlice", GCCC);
1443 
1444   PrepareForCycleCollectionSlice(CCReason::API, TimeStamp());
1445 
1446   js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
1447   nsCycleCollector_collectSlice(budget, CCReason::API);
1448 
1449   sCCStats.AfterCycleCollectionSlice();
1450 }
1451 
ClearMaxCCSliceTime()1452 void nsJSContext::ClearMaxCCSliceTime() {
1453   sCCStats.mMaxSliceTimeSinceClear = TimeDuration();
1454 }
1455 
GetMaxCCSliceTimeSinceClear()1456 uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
1457   return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
1458 }
1459 
1460 // static
BeginCycleCollectionCallback(CCReason aReason)1461 void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) {
1462   MOZ_ASSERT(NS_IsMainThread());
1463 
1464   TimeStamp startTime = TimeStamp::Now();
1465   sCCStats.mBeginTime =
1466       sCCStats.mBeginSliceTime.IsNull() ? startTime : sCCStats.mBeginSliceTime;
1467   sCCStats.mSuspected = nsCycleCollector_suspectedCount();
1468 
1469   // Run forgetSkippable synchronously to reduce the size of the CC graph. This
1470   // is particularly useful if we recently finished a GC.
1471   if (sScheduler.IsEarlyForgetSkippable()) {
1472     while (sScheduler.IsEarlyForgetSkippable()) {
1473       FireForgetSkippable(false, TimeStamp());
1474     }
1475     sCCStats.AfterSyncForgetSkippable(startTime);
1476   }
1477 
1478   if (sShuttingDown) {
1479     return;
1480   }
1481 
1482   sScheduler.InitCCRunnerStateMachine(
1483       mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason);
1484   sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
1485 }
1486 
1487 // static
EndCycleCollectionCallback(CycleCollectorResults & aResults)1488 void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
1489   MOZ_ASSERT(NS_IsMainThread());
1490 
1491   sScheduler.KillCCRunner();
1492 
1493   // Update timing information for the current slice before we log it, if
1494   // we previously called PrepareForCycleCollectionSlice(). During shutdown
1495   // CCs, this won't happen.
1496   sCCStats.AfterCycleCollectionSlice();
1497   sScheduler.NoteCycleCollected(aResults);
1498 
1499   TimeStamp endCCTimeStamp = TimeStamp::Now();
1500   TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
1501 
1502   if (sScheduler.NeedsGCAfterCC()) {
1503     MOZ_ASSERT(
1504         TimeDuration::FromMilliseconds(
1505             StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration,
1506         "A max duration ICC shouldn't reduce GC delay to 0");
1507 
1508     sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr,
1509                       TimeDuration::FromMilliseconds(
1510                           StaticPrefs::javascript_options_gc_delay()) -
1511                           std::min(ccNowDuration, kMaxICCDuration));
1512   }
1513 
1514   // Log information about the CC via telemetry, JSON and the console.
1515 
1516   sCCStats.SendTelemetry(ccNowDuration);
1517 
1518   uint32_t cleanups = std::max(sCCStats.mForgetSkippableBeforeCC, 1u);
1519 
1520   sCCStats.MaybeLogStats(aResults, cleanups);
1521 
1522   sCCStats.MaybeNotifyStats(aResults, ccNowDuration, cleanups);
1523 
1524   // Update global state to indicate we have just run a cycle collection.
1525   sScheduler.NoteCCEnd(endCCTimeStamp);
1526   sCCStats.Clear();
1527 }
1528 
1529 /* static */
CCRunnerFired(TimeStamp aDeadline)1530 bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) {
1531   AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
1532 
1533   bool didDoWork = false;
1534 
1535   // The CC/GC scheduler (sScheduler) decides what action(s) to take during
1536   // this invocation of the CC runner.
1537   //
1538   // This may be zero, one, or multiple actions. (Zero is when CC is blocked by
1539   // incremental GC, or when the scheduler determined that a CC is no longer
1540   // needed.) Loop until the scheduler finishes this invocation by returning
1541   // `Yield` in step.mYield.
1542   CCRunnerStep step;
1543   do {
1544     step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(),
1545                                       nsCycleCollector_suspectedCount());
1546     switch (step.mAction) {
1547       case CCRunnerAction::None:
1548         break;
1549 
1550       case CCRunnerAction::ForgetSkippable:
1551         // 'Forget skippable' only, then end this invocation.
1552         FireForgetSkippable(bool(step.mRemoveChildless), aDeadline);
1553         break;
1554 
1555       case CCRunnerAction::CleanupContentUnbinder:
1556         // Clear content unbinder before the first actual CC slice.
1557         Element::ClearContentUnbinder();
1558         break;
1559 
1560       case CCRunnerAction::CleanupDeferred:
1561         // and if time still permits, perform deferred deletions.
1562         nsCycleCollector_doDeferredDeletion();
1563         break;
1564 
1565       case CCRunnerAction::CycleCollect:
1566         // Cycle collection slice.
1567         nsJSContext::RunCycleCollectorSlice(step.mCCReason, aDeadline);
1568         break;
1569 
1570       case CCRunnerAction::StopRunning:
1571         // End this CC, either because we have run a cycle collection slice, or
1572         // because a CC is no longer needed.
1573         sScheduler.KillCCRunner();
1574         break;
1575     }
1576 
1577     if (step.mAction != CCRunnerAction::None) {
1578       didDoWork = true;
1579     }
1580   } while (step.mYield == CCRunnerYield::Continue);
1581 
1582   return didDoWork;
1583 }
1584 
1585 // static
HasHadCleanupSinceLastGC()1586 bool nsJSContext::HasHadCleanupSinceLastGC() {
1587   return sScheduler.IsEarlyForgetSkippable(1);
1588 }
1589 
1590 // static
RunNextCollectorTimer(JS::GCReason aReason,mozilla::TimeStamp aDeadline)1591 void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
1592                                         mozilla::TimeStamp aDeadline) {
1593   sScheduler.RunNextCollectorTimer(aReason, aDeadline);
1594 }
1595 
1596 // static
MaybeRunNextCollectorSlice(nsIDocShell * aDocShell,JS::GCReason aReason)1597 void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
1598                                              JS::GCReason aReason) {
1599   if (!aDocShell || !XRE_IsContentProcess()) {
1600     return;
1601   }
1602 
1603   BrowsingContext* bc = aDocShell->GetBrowsingContext();
1604   if (!bc) {
1605     return;
1606   }
1607 
1608   BrowsingContext* root = bc->Top();
1609   if (bc == root) {
1610     // We don't want to run collectors when loading the top level page.
1611     return;
1612   }
1613 
1614   nsIDocShell* rootDocShell = root->GetDocShell();
1615   if (!rootDocShell) {
1616     return;
1617   }
1618 
1619   Document* rootDocument = rootDocShell->GetDocument();
1620   if (!rootDocument ||
1621       rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE ||
1622       rootDocument->IsInBackgroundWindow()) {
1623     return;
1624   }
1625 
1626   PresShell* presShell = rootDocument->GetPresShell();
1627   if (!presShell) {
1628     return;
1629   }
1630 
1631   nsViewManager* vm = presShell->GetViewManager();
1632   if (!vm) {
1633     return;
1634   }
1635 
1636   if (!sScheduler.IsUserActive()) {
1637     Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
1638     // Try to not delay the next RefreshDriver tick, so give a reasonable
1639     // deadline for collectors.
1640     if (next.isSome()) {
1641       sScheduler.RunNextCollectorTimer(aReason, next.value());
1642     }
1643   }
1644 }
1645 
1646 // static
PokeGC(JS::GCReason aReason,JSObject * aObj,TimeDuration aDelay)1647 void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
1648                          TimeDuration aDelay) {
1649   sScheduler.PokeGC(aReason, aObj, aDelay);
1650 }
1651 
1652 // static
DoLowMemoryGC()1653 void nsJSContext::DoLowMemoryGC() {
1654   if (sShuttingDown) {
1655     return;
1656   }
1657   nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1658                                  nsJSContext::ShrinkingGC);
1659   nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE);
1660   if (sScheduler.NeedsGCAfterCC()) {
1661     nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
1662                                    nsJSContext::ShrinkingGC);
1663   }
1664 }
1665 
1666 // static
LowMemoryGC()1667 void nsJSContext::LowMemoryGC() {
1668   RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
1669       CCGCScheduler::MayGCNow(JS::GCReason::MEM_PRESSURE);
1670   if (!mbPromise) {
1671     // Normally when the promise is null it means that IPC failed, that probably
1672     // means that something bad happened, don't bother with the GC.
1673     return;
1674   }
1675   mbPromise->Then(
1676       GetMainThreadSerialEventTarget(), __func__,
1677       [](bool aIgnored) { DoLowMemoryGC(); },
1678       [](mozilla::ipc::ResponseRejectReason r) {});
1679 }
1680 
1681 // static
MaybePokeCC()1682 void nsJSContext::MaybePokeCC() {
1683   sScheduler.MaybePokeCC(TimeStamp::Now(), nsCycleCollector_suspectedCount());
1684 }
1685 
DOMGCSliceCallback(JSContext * aCx,JS::GCProgress aProgress,const JS::GCDescription & aDesc)1686 static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
1687                                const JS::GCDescription& aDesc) {
1688   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
1689 
1690   static TimeStamp sCurrentGCStartTime;
1691 
1692   switch (aProgress) {
1693     case JS::GC_CYCLE_BEGIN: {
1694       // Prevent cycle collections and shrinking during incremental GC.
1695       sScheduler.NoteGCBegin();
1696       sCurrentGCStartTime = TimeStamp::Now();
1697       break;
1698     }
1699 
1700     case JS::GC_CYCLE_END: {
1701       TimeDuration delta = GetCollectionTimeDelta();
1702 
1703       if (StaticPrefs::javascript_options_mem_log()) {
1704         nsString gcstats;
1705         gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
1706         nsAutoString prefix;
1707         nsTextFormatter::ssprintf(prefix, u"GC(T+%.1f)[%s-%i] ",
1708                                   delta.ToSeconds(),
1709                                   ProcessNameForCollectorLog(), getpid());
1710         nsString msg = prefix + gcstats;
1711         nsCOMPtr<nsIConsoleService> cs =
1712             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1713         if (cs) {
1714           cs->LogStringMessage(msg.get());
1715         }
1716       }
1717 
1718       sScheduler.NoteGCEnd();
1719 
1720       // May need to kill the GC runner
1721       sScheduler.KillGCRunner();
1722 
1723       TimeStamp now = TimeStamp::Now();
1724       sScheduler.MaybePokeCC(now, nsCycleCollector_suspectedCount());
1725 
1726       if (aDesc.isZone_) {
1727         sScheduler.PokeFullGC();
1728       } else {
1729         sScheduler.SetNeedsFullGC(false);
1730         sScheduler.KillFullGCTimer();
1731       }
1732 
1733       if (sScheduler.IsCCNeeded(now, nsCycleCollector_suspectedCount()) !=
1734           CCReason::NO_REASON) {
1735         nsCycleCollector_dispatchDeferredDeletion();
1736       }
1737 
1738       Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
1739                             TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
1740       break;
1741     }
1742 
1743     case JS::GC_SLICE_BEGIN:
1744       break;
1745 
1746     case JS::GC_SLICE_END:
1747       sScheduler.NoteGCSliceEnd(aDesc.lastSliceEnd(aCx) -
1748                                 aDesc.lastSliceStart(aCx));
1749 
1750       if (sShuttingDown) {
1751         sScheduler.KillGCRunner();
1752       } else {
1753         // If incremental GC wasn't triggered by GCTimerFired, we may not have a
1754         // runner to ensure all the slices are handled. So, create the runner
1755         // here.
1756         sScheduler.EnsureGCRunner(0);
1757       }
1758 
1759       if (sScheduler.IsCCNeeded(TimeStamp::Now(),
1760                                 nsCycleCollector_suspectedCount()) !=
1761           CCReason::NO_REASON) {
1762         nsCycleCollector_dispatchDeferredDeletion();
1763       }
1764 
1765       if (StaticPrefs::javascript_options_mem_log()) {
1766         nsString gcstats;
1767         gcstats.Adopt(aDesc.formatSliceMessage(aCx));
1768         nsAutoString prefix;
1769         nsTextFormatter::ssprintf(prefix, u"[%s-%i] ",
1770                                   ProcessNameForCollectorLog(), getpid());
1771         nsString msg = prefix + gcstats;
1772         nsCOMPtr<nsIConsoleService> cs =
1773             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1774         if (cs) {
1775           cs->LogStringMessage(msg.get());
1776         }
1777       }
1778 
1779       break;
1780 
1781     default:
1782       MOZ_CRASH("Unexpected GCProgress value");
1783   }
1784 
1785   if (sPrevGCSliceCallback) {
1786     (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
1787   }
1788 }
1789 
SetWindowProxy(JS::Handle<JSObject * > aWindowProxy)1790 void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
1791   mWindowProxy = aWindowProxy;
1792 }
1793 
GetWindowProxy()1794 JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
1795 
LikelyShortLivingObjectCreated()1796 void nsJSContext::LikelyShortLivingObjectCreated() {
1797   ++sScheduler.mLikelyShortLivingObjectsNeedingGC;
1798 }
1799 
StartupJSEnvironment()1800 void mozilla::dom::StartupJSEnvironment() {
1801   // initialize all our statics, so that we can restart XPCOM
1802   sIsInitialized = false;
1803   sShuttingDown = false;
1804   new (&sScheduler) CCGCScheduler();  // Reset the scheduler state.
1805   sCCStats.Init();
1806 }
1807 
SetGCParameter(JSGCParamKey aParam,uint32_t aValue)1808 static void SetGCParameter(JSGCParamKey aParam, uint32_t aValue) {
1809   AutoJSAPI jsapi;
1810   jsapi.Init();
1811   JS_SetGCParameter(jsapi.cx(), aParam, aValue);
1812 }
1813 
ResetGCParameter(JSGCParamKey aParam)1814 static void ResetGCParameter(JSGCParamKey aParam) {
1815   AutoJSAPI jsapi;
1816   jsapi.Init();
1817   JS_ResetGCParameter(jsapi.cx(), aParam);
1818 }
1819 
SetMemoryPrefChangedCallbackMB(const char * aPrefName,void * aClosure)1820 static void SetMemoryPrefChangedCallbackMB(const char* aPrefName,
1821                                            void* aClosure) {
1822   int32_t prefMB = Preferences::GetInt(aPrefName, -1);
1823   // handle overflow and negative pref values
1824   CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
1825   if (prefB.isValid() && prefB.value() >= 0) {
1826     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1827   } else {
1828     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1829   }
1830 }
1831 
SetMemoryNurseryPrefChangedCallback(const char * aPrefName,void * aClosure)1832 static void SetMemoryNurseryPrefChangedCallback(const char* aPrefName,
1833                                                 void* aClosure) {
1834   int32_t prefKB = Preferences::GetInt(aPrefName, -1);
1835   // handle overflow and negative pref values
1836   CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefKB) * 1024;
1837   if (prefB.isValid() && prefB.value() >= 0) {
1838     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
1839   } else {
1840     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1841   }
1842 }
1843 
SetMemoryPrefChangedCallbackInt(const char * aPrefName,void * aClosure)1844 static void SetMemoryPrefChangedCallbackInt(const char* aPrefName,
1845                                             void* aClosure) {
1846   int32_t pref = Preferences::GetInt(aPrefName, -1);
1847   // handle overflow and negative pref values
1848   if (pref >= 0 && pref < 10000) {
1849     SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1850   } else {
1851     ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
1852   }
1853 }
1854 
SetMemoryPrefChangedCallbackBool(const char * aPrefName,void * aClosure)1855 static void SetMemoryPrefChangedCallbackBool(const char* aPrefName,
1856                                              void* aClosure) {
1857   bool pref = Preferences::GetBool(aPrefName);
1858   SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
1859 }
1860 
SetMemoryGCSliceTimePrefChangedCallback(const char * aPrefName,void * aClosure)1861 static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName,
1862                                                     void* aClosure) {
1863   int32_t pref = Preferences::GetInt(aPrefName, -1);
1864   // handle overflow and negative pref values
1865   if (pref > 0 && pref < 100000) {
1866     sScheduler.SetActiveIntersliceGCBudget(
1867         TimeDuration::FromMilliseconds(pref));
1868     SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref);
1869   } else {
1870     ResetGCParameter(JSGC_SLICE_TIME_BUDGET_MS);
1871   }
1872 }
1873 
SetIncrementalCCPrefChangedCallback(const char * aPrefName,void * aClosure)1874 static void SetIncrementalCCPrefChangedCallback(const char* aPrefName,
1875                                                 void* aClosure) {
1876   bool pref = Preferences::GetBool(aPrefName);
1877   sIncrementalCC = pref;
1878 }
1879 
1880 class JSDispatchableRunnable final : public Runnable {
~JSDispatchableRunnable()1881   ~JSDispatchableRunnable() { MOZ_ASSERT(!mDispatchable); }
1882 
1883  public:
JSDispatchableRunnable(JS::Dispatchable * aDispatchable)1884   explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
1885       : mozilla::Runnable("JSDispatchableRunnable"),
1886         mDispatchable(aDispatchable) {
1887     MOZ_ASSERT(mDispatchable);
1888   }
1889 
1890  protected:
Run()1891   NS_IMETHOD Run() override {
1892     MOZ_ASSERT(NS_IsMainThread());
1893 
1894     AutoJSAPI jsapi;
1895     jsapi.Init();
1896 
1897     JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
1898         sShuttingDown ? JS::Dispatchable::ShuttingDown
1899                       : JS::Dispatchable::NotShuttingDown;
1900 
1901     mDispatchable->run(jsapi.cx(), maybeShuttingDown);
1902     mDispatchable = nullptr;  // mDispatchable may delete itself
1903 
1904     return NS_OK;
1905   }
1906 
1907  private:
1908   JS::Dispatchable* mDispatchable;
1909 };
1910 
DispatchToEventLoop(void * closure,JS::Dispatchable * aDispatchable)1911 static bool DispatchToEventLoop(void* closure,
1912                                 JS::Dispatchable* aDispatchable) {
1913   MOZ_ASSERT(!closure);
1914 
1915   // This callback may execute either on the main thread or a random JS-internal
1916   // helper thread. This callback can be called during shutdown so we cannot
1917   // simply NS_DispatchToMainThread. Failure during shutdown is expected and
1918   // properly handled by the JS engine.
1919 
1920   nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
1921   if (!mainTarget) {
1922     return false;
1923   }
1924 
1925   RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
1926   MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
1927   return true;
1928 }
1929 
ConsumeStream(JSContext * aCx,JS::HandleObject aObj,JS::MimeType aMimeType,JS::StreamConsumer * aConsumer)1930 static bool ConsumeStream(JSContext* aCx, JS::HandleObject aObj,
1931                           JS::MimeType aMimeType,
1932                           JS::StreamConsumer* aConsumer) {
1933   return FetchUtil::StreamResponseToJS(aCx, aObj, aMimeType, aConsumer,
1934                                        nullptr);
1935 }
1936 
CreateGCSliceBudget(JS::GCReason aReason,int64_t aMillis)1937 static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason,
1938                                            int64_t aMillis) {
1939   return sScheduler.CreateGCSliceBudget(
1940       mozilla::TimeDuration::FromMilliseconds(aMillis), false, false);
1941 }
1942 
EnsureStatics()1943 void nsJSContext::EnsureStatics() {
1944   if (sIsInitialized) {
1945     if (!nsContentUtils::XPConnect()) {
1946       MOZ_CRASH();
1947     }
1948     return;
1949   }
1950 
1951   // Let's make sure that our main thread is the same as the xpcom main thread.
1952   MOZ_ASSERT(NS_IsMainThread());
1953 
1954   AutoJSAPI jsapi;
1955   jsapi.Init();
1956 
1957   sPrevGCSliceCallback = JS::SetGCSliceCallback(jsapi.cx(), DOMGCSliceCallback);
1958 
1959   JS::SetCreateGCSliceBudgetCallback(jsapi.cx(), CreateGCSliceBudget);
1960 
1961   JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
1962   JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream,
1963                                 FetchUtil::ReportJSStreamError);
1964 
1965   // Set these global xpconnect options...
1966   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
1967                                        "javascript.options.mem.max",
1968                                        (void*)JSGC_MAX_BYTES);
1969   Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
1970                                        "javascript.options.mem.nursery.min_kb",
1971                                        (void*)JSGC_MIN_NURSERY_BYTES);
1972   Preferences::RegisterCallbackAndCall(SetMemoryNurseryPrefChangedCallback,
1973                                        "javascript.options.mem.nursery.max_kb",
1974                                        (void*)JSGC_MAX_NURSERY_BYTES);
1975 
1976   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1977                                        "javascript.options.mem.gc_per_zone",
1978                                        (void*)JSGC_PER_ZONE_GC_ENABLED);
1979 
1980   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1981                                        "javascript.options.mem.gc_incremental",
1982                                        (void*)JSGC_INCREMENTAL_GC_ENABLED);
1983 
1984   Preferences::RegisterCallbackAndCall(
1985       SetMemoryGCSliceTimePrefChangedCallback,
1986       "javascript.options.mem.gc_incremental_slice_ms");
1987 
1988   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
1989                                        "javascript.options.mem.gc_compacting",
1990                                        (void*)JSGC_COMPACTING_ENABLED);
1991 
1992   Preferences::RegisterCallbackAndCall(
1993       SetMemoryPrefChangedCallbackBool,
1994       "javascript.options.mem.incremental_weakmap",
1995       (void*)JSGC_INCREMENTAL_WEAKMAP_ENABLED);
1996 
1997   Preferences::RegisterCallbackAndCall(
1998       SetMemoryPrefChangedCallbackInt,
1999       "javascript.options.mem.gc_high_frequency_time_limit_ms",
2000       (void*)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
2001 
2002   Preferences::RegisterCallbackAndCall(
2003       SetMemoryPrefChangedCallbackInt,
2004       "javascript.options.mem.gc_low_frequency_heap_growth",
2005       (void*)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
2006 
2007   Preferences::RegisterCallbackAndCall(
2008       SetMemoryPrefChangedCallbackInt,
2009       "javascript.options.mem.gc_high_frequency_large_heap_growth",
2010       (void*)JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH);
2011 
2012   Preferences::RegisterCallbackAndCall(
2013       SetMemoryPrefChangedCallbackInt,
2014       "javascript.options.mem.gc_high_frequency_small_heap_growth",
2015       (void*)JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH);
2016 
2017   Preferences::RegisterCallbackAndCall(
2018       SetMemoryPrefChangedCallbackInt,
2019       "javascript.options.mem.gc_small_heap_size_max_mb",
2020       (void*)JSGC_SMALL_HEAP_SIZE_MAX);
2021 
2022   Preferences::RegisterCallbackAndCall(
2023       SetMemoryPrefChangedCallbackInt,
2024       "javascript.options.mem.gc_large_heap_size_min_mb",
2025       (void*)JSGC_LARGE_HEAP_SIZE_MIN);
2026 
2027   Preferences::RegisterCallbackAndCall(
2028       SetMemoryPrefChangedCallbackInt,
2029       "javascript.options.mem.gc_allocation_threshold_mb",
2030       (void*)JSGC_ALLOCATION_THRESHOLD);
2031 
2032   Preferences::RegisterCallbackAndCall(
2033       SetMemoryPrefChangedCallbackInt,
2034       "javascript.options.mem.gc_malloc_threshold_base_mb",
2035       (void*)JSGC_MALLOC_THRESHOLD_BASE);
2036 
2037   Preferences::RegisterCallbackAndCall(
2038       SetMemoryPrefChangedCallbackInt,
2039       "javascript.options.mem.gc_small_heap_incremental_limit",
2040       (void*)JSGC_SMALL_HEAP_INCREMENTAL_LIMIT);
2041   Preferences::RegisterCallbackAndCall(
2042       SetMemoryPrefChangedCallbackInt,
2043       "javascript.options.mem.gc_large_heap_incremental_limit",
2044       (void*)JSGC_LARGE_HEAP_INCREMENTAL_LIMIT);
2045 
2046   Preferences::RegisterCallbackAndCall(
2047       SetMemoryPrefChangedCallbackInt,
2048       "javascript.options.mem.gc_urgent_threshold_mb",
2049       (void*)JSGC_URGENT_THRESHOLD_MB);
2050 
2051   Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
2052                                        "dom.cycle_collector.incremental");
2053 
2054   Preferences::RegisterCallbackAndCall(
2055       SetMemoryPrefChangedCallbackInt,
2056       "javascript.options.mem.gc_min_empty_chunk_count",
2057       (void*)JSGC_MIN_EMPTY_CHUNK_COUNT);
2058 
2059   Preferences::RegisterCallbackAndCall(
2060       SetMemoryPrefChangedCallbackInt,
2061       "javascript.options.mem.gc_max_empty_chunk_count",
2062       (void*)JSGC_MAX_EMPTY_CHUNK_COUNT);
2063 
2064   Preferences::RegisterCallbackAndCall(
2065       SetMemoryPrefChangedCallbackInt,
2066       "javascript.options.mem.gc_helper_thread_ratio",
2067       (void*)JSGC_HELPER_THREAD_RATIO);
2068 
2069   Preferences::RegisterCallbackAndCall(
2070       SetMemoryPrefChangedCallbackInt,
2071       "javascript.options.mem.gc_max_helper_threads",
2072       (void*)JSGC_MAX_HELPER_THREADS);
2073 
2074   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2075   if (!obs) {
2076     MOZ_CRASH();
2077   }
2078 
2079   nsIObserver* observer = new nsJSEnvironmentObserver();
2080   obs->AddObserver(observer, "memory-pressure", false);
2081   obs->AddObserver(observer, "user-interaction-inactive", false);
2082   obs->AddObserver(observer, "user-interaction-active", false);
2083   obs->AddObserver(observer, "quit-application", false);
2084   obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
2085   obs->AddObserver(observer, "content-child-will-shutdown", false);
2086 
2087   sIsInitialized = true;
2088 }
2089 
ShutdownJSEnvironment()2090 void mozilla::dom::ShutdownJSEnvironment() {
2091   sShuttingDown = true;
2092   sScheduler.Shutdown();
2093 }
2094 
AsyncErrorReporter(xpc::ErrorReport * aReport)2095 AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
2096     : Runnable("dom::AsyncErrorReporter"), mReport(aReport) {}
2097 
SerializeStack(JSContext * aCx,JS::Handle<JSObject * > aStack)2098 void AsyncErrorReporter::SerializeStack(JSContext* aCx,
2099                                         JS::Handle<JSObject*> aStack) {
2100   mStackHolder = MakeUnique<SerializedStackHolder>();
2101   mStackHolder->SerializeMainThreadOrWorkletStack(aCx, aStack);
2102 }
2103 
SetException(JSContext * aCx,JS::Handle<JS::Value> aException)2104 void AsyncErrorReporter::SetException(JSContext* aCx,
2105                                       JS::Handle<JS::Value> aException) {
2106   MOZ_ASSERT(NS_IsMainThread());
2107   mException.init(aCx, aException);
2108   mHasException = true;
2109 }
2110 
Run()2111 NS_IMETHODIMP AsyncErrorReporter::Run() {
2112   AutoJSAPI jsapi;
2113   // We're only using this context to deserialize a stack to report to the
2114   // console, so the scope we use doesn't matter. Stack frame filtering happens
2115   // based on the principal encoded into the frame and the caller compartment,
2116   // not the compartment of the frame object, and the console reporting code
2117   // will not be using our context, and therefore will not care what compartment
2118   // it has entered.
2119   DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
2120   MOZ_ASSERT(ok, "Problem with system global?");
2121   JSContext* cx = jsapi.cx();
2122   JS::Rooted<JSObject*> stack(cx);
2123   JS::Rooted<JSObject*> stackGlobal(cx);
2124   if (mStackHolder) {
2125     stack = mStackHolder->ReadStack(cx);
2126     if (stack) {
2127       stackGlobal = JS::CurrentGlobalOrNull(cx);
2128     }
2129   }
2130 
2131   JS::Rooted<Maybe<JS::Value>> exception(cx, Nothing());
2132   if (mHasException) {
2133     MOZ_ASSERT(NS_IsMainThread());
2134     exception = Some(mException);
2135     // Remove our reference to the exception.
2136     mException.setUndefined();
2137     mHasException = false;
2138   }
2139 
2140   mReport->LogToConsoleWithStack(nullptr, exception, stack, stackGlobal);
2141   return NS_OK;
2142 }
2143 
2144 // A fast-array class for JS.  This class supports both nsIJSScriptArray and
2145 // nsIArray.  If it is JS itself providing and consuming this class, all work
2146 // can be done via nsIJSScriptArray, and avoid the conversion of elements
2147 // to/from nsISupports.
2148 // When consumed by non-JS (eg, another script language), conversion is done
2149 // on-the-fly.
2150 class nsJSArgArray final : public nsIJSArgArray {
2151  public:
2152   nsJSArgArray(JSContext* aContext, uint32_t argc, const JS::Value* argv,
2153                nsresult* prv);
2154 
2155   // nsISupports
2156   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
2157   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSArgArray,
2158                                                          nsIJSArgArray)
2159 
2160   // nsIArray
2161   NS_DECL_NSIARRAY
2162 
2163   // nsIJSArgArray
2164   nsresult GetArgs(uint32_t* argc, void** argv) override;
2165 
2166   void ReleaseJSObjects();
2167 
2168  protected:
2169   ~nsJSArgArray();
2170   JSContext* mContext;
2171   JS::Heap<JS::Value>* mArgv;
2172   uint32_t mArgc;
2173 };
2174 
nsJSArgArray(JSContext * aContext,uint32_t argc,const JS::Value * argv,nsresult * prv)2175 nsJSArgArray::nsJSArgArray(JSContext* aContext, uint32_t argc,
2176                            const JS::Value* argv, nsresult* prv)
2177     : mContext(aContext), mArgv(nullptr), mArgc(argc) {
2178   // copy the array - we don't know its lifetime, and ours is tied to xpcom
2179   // refcounting.
2180   if (argc) {
2181     mArgv = new (fallible) JS::Heap<JS::Value>[argc];
2182     if (!mArgv) {
2183       *prv = NS_ERROR_OUT_OF_MEMORY;
2184       return;
2185     }
2186   }
2187 
2188   // Callers are allowed to pass in a null argv even for argc > 0. They can
2189   // then use GetArgs to initialize the values.
2190   if (argv) {
2191     for (uint32_t i = 0; i < argc; ++i) mArgv[i] = argv[i];
2192   }
2193 
2194   if (argc > 0) {
2195     mozilla::HoldJSObjects(this);
2196   }
2197 
2198   *prv = NS_OK;
2199 }
2200 
~nsJSArgArray()2201 nsJSArgArray::~nsJSArgArray() { ReleaseJSObjects(); }
2202 
ReleaseJSObjects()2203 void nsJSArgArray::ReleaseJSObjects() {
2204   delete[] mArgv;
2205 
2206   if (mArgc > 0) {
2207     mArgc = 0;
2208     mozilla::DropJSObjects(this);
2209   }
2210 }
2211 
2212 // QueryInterface implementation for nsJSArgArray
2213 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(nsJSArgArray)
2214 
2215 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSArgArray)
2216   tmp->ReleaseJSObjects();
2217 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2218 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSArgArray)
2219 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2220 
2221 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSArgArray)
2222   if (tmp->mArgv) {
2223     for (uint32_t i = 0; i < tmp->mArgc; ++i) {
2224       NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArgv[i])
2225     }
2226   }
2227 NS_IMPL_CYCLE_COLLECTION_TRACE_END
2228 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)2229 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSArgArray)
2230   NS_INTERFACE_MAP_ENTRY(nsIArray)
2231   NS_INTERFACE_MAP_ENTRY(nsIJSArgArray)
2232   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSArgArray)
2233 NS_INTERFACE_MAP_END
2234 
2235 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSArgArray)
2236 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSArgArray)
2237 
2238 nsresult nsJSArgArray::GetArgs(uint32_t* argc, void** argv) {
2239   *argv = (void*)mArgv;
2240   *argc = mArgc;
2241   return NS_OK;
2242 }
2243 
2244 // nsIArray impl
GetLength(uint32_t * aLength)2245 NS_IMETHODIMP nsJSArgArray::GetLength(uint32_t* aLength) {
2246   *aLength = mArgc;
2247   return NS_OK;
2248 }
2249 
QueryElementAt(uint32_t index,const nsIID & uuid,void ** result)2250 NS_IMETHODIMP nsJSArgArray::QueryElementAt(uint32_t index, const nsIID& uuid,
2251                                            void** result) {
2252   *result = nullptr;
2253   if (index >= mArgc) return NS_ERROR_INVALID_ARG;
2254 
2255   if (uuid.Equals(NS_GET_IID(nsIVariant)) ||
2256       uuid.Equals(NS_GET_IID(nsISupports))) {
2257     // Have to copy a Heap into a Rooted to work with it.
2258     JS::Rooted<JS::Value> val(mContext, mArgv[index]);
2259     return nsContentUtils::XPConnect()->JSToVariant(mContext, val,
2260                                                     (nsIVariant**)result);
2261   }
2262   NS_WARNING("nsJSArgArray only handles nsIVariant");
2263   return NS_ERROR_NO_INTERFACE;
2264 }
2265 
IndexOf(uint32_t startIndex,nsISupports * element,uint32_t * _retval)2266 NS_IMETHODIMP nsJSArgArray::IndexOf(uint32_t startIndex, nsISupports* element,
2267                                     uint32_t* _retval) {
2268   return NS_ERROR_NOT_IMPLEMENTED;
2269 }
2270 
ScriptedEnumerate(const nsIID & aElemIID,uint8_t aArgc,nsISimpleEnumerator ** aResult)2271 NS_IMETHODIMP nsJSArgArray::ScriptedEnumerate(const nsIID& aElemIID,
2272                                               uint8_t aArgc,
2273                                               nsISimpleEnumerator** aResult) {
2274   return NS_ERROR_NOT_IMPLEMENTED;
2275 }
2276 
EnumerateImpl(const nsID & aEntryIID,nsISimpleEnumerator ** _retval)2277 NS_IMETHODIMP nsJSArgArray::EnumerateImpl(const nsID& aEntryIID,
2278                                           nsISimpleEnumerator** _retval) {
2279   return NS_ERROR_NOT_IMPLEMENTED;
2280 }
2281 
2282 // The factory function
NS_CreateJSArgv(JSContext * aContext,uint32_t argc,const JS::Value * argv,nsIJSArgArray ** aArray)2283 nsresult NS_CreateJSArgv(JSContext* aContext, uint32_t argc,
2284                          const JS::Value* argv, nsIJSArgArray** aArray) {
2285   nsresult rv;
2286   nsCOMPtr<nsIJSArgArray> ret = new nsJSArgArray(aContext, argc, argv, &rv);
2287   if (NS_FAILED(rv)) {
2288     return rv;
2289   }
2290   ret.forget(aArray);
2291   return NS_OK;
2292 }
2293