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