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