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