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