Coverage Report

Created: 2018-09-25 14:53

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