XPCJSRuntime.cpp (129541B)
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 /* Per JSRuntime object */ 8 9 #include "mozilla/ArrayUtils.h" 10 #include "mozilla/AutoRestore.h" 11 #include "mozilla/AppShutdown.h" 12 #include "mozilla/MemoryReporting.h" 13 #include "mozilla/UniquePtr.h" 14 15 #include "xpcprivate.h" 16 #include "xpcpublic.h" 17 #include "XPCMaps.h" 18 #include "XPCJSMemoryReporter.h" 19 #include "XrayWrapper.h" 20 #include "WrapperFactory.h" 21 #include "mozJSModuleLoader.h" 22 #include "nsNetUtil.h" 23 #include "nsContentSecurityUtils.h" 24 25 #include "nsExceptionHandler.h" 26 #include "nsIMemoryInfoDumper.h" 27 #include "nsIMemoryReporter.h" 28 #include "nsIObserverService.h" 29 #include "mozilla/dom/Document.h" 30 #include "nsIRunnable.h" 31 #include "nsPIDOMWindow.h" 32 #include "nsPrintfCString.h" 33 #include "nsScriptSecurityManager.h" 34 #include "nsWindowSizes.h" 35 #include "mozilla/BasePrincipal.h" 36 #include "mozilla/Preferences.h" 37 #include "mozilla/Services.h" 38 #include "mozilla/dom/ScriptLoader.h" 39 #include "mozilla/dom/ScriptSettings.h" 40 #include "mozilla/glean/JsXpconnectMetrics.h" 41 #include "mozilla/glean/XpcomMetrics.h" 42 43 #include "nsContentUtils.h" 44 #include "nsCCUncollectableMarker.h" 45 #include "nsCycleCollectionNoteRootCallback.h" 46 #include "nsCycleCollector.h" 47 #include "jsapi.h" 48 #include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp 49 #include "js/experimental/SourceHook.h" // js::{,Set}SourceHook 50 #include "js/GCAPI.h" 51 #include "js/MemoryFunctions.h" 52 #include "js/MemoryMetrics.h" 53 #include "js/Object.h" // JS::GetClass 54 #include "js/RealmIterators.h" 55 #include "js/SliceBudget.h" 56 #include "js/UbiNode.h" 57 #include "js/UbiNodeUtils.h" 58 #include "js/friend/UsageStatistics.h" // JSMetric, JS_SetAccumulateTelemetryCallback 59 #include "js/friend/WindowProxy.h" // js::SetWindowProxyClass 60 #include "js/friend/XrayJitInfo.h" // JS::SetXrayJitInfo 61 #include "js/Utility.h" // JS::UniqueTwoByteChars 62 #include "mozilla/dom/AbortSignalBinding.h" 63 #include "mozilla/dom/GeneratedAtomList.h" 64 #include "mozilla/dom/BindingUtils.h" 65 #include "mozilla/dom/Element.h" 66 #include "mozilla/dom/FetchUtil.h" 67 #include "mozilla/dom/WindowBinding.h" 68 #include "mozilla/Attributes.h" 69 #include "mozilla/ProcessHangMonitor.h" 70 #include "mozilla/ProfilerLabels.h" 71 #include "mozilla/Sprintf.h" 72 #include "AccessCheck.h" 73 #include "nsGlobalWindowInner.h" 74 #include "nsAboutProtocolUtils.h" 75 76 #include "NodeUbiReporting.h" 77 #include "ExpandedPrincipal.h" 78 #include "nsIInputStream.h" 79 #include "nsJSPrincipals.h" 80 #include "nsJSEnvironment.h" 81 #include "XPCInlines.h" 82 83 #ifdef XP_WIN 84 # include <windows.h> 85 #endif 86 87 using namespace mozilla; 88 using namespace mozilla::dom; 89 using namespace xpc; 90 using namespace JS; 91 using namespace js; 92 using mozilla::dom::PerThreadAtomCache; 93 94 /***************************************************************************/ 95 96 const char* const XPCJSRuntime::mStrings[] = { 97 "constructor", // IDX_CONSTRUCTOR 98 "toString", // IDX_TO_STRING 99 "toSource", // IDX_TO_SOURCE 100 "value", // IDX_VALUE 101 "QueryInterface", // IDX_QUERY_INTERFACE 102 "Components", // IDX_COMPONENTS 103 "Cc", // IDX_CC 104 "Ci", // IDX_CI 105 "Cr", // IDX_CR 106 "Cu", // IDX_CU 107 "Services", // IDX_SERVICES 108 "wrappedJSObject", // IDX_WRAPPED_JSOBJECT 109 "prototype", // IDX_PROTOTYPE 110 "eval", // IDX_EVAL 111 "controllers", // IDX_CONTROLLERS 112 "Controllers", // IDX_CONTROLLERS_CLASS 113 "length", // IDX_LENGTH 114 "name", // IDX_NAME 115 "undefined", // IDX_UNDEFINED 116 "", // IDX_EMPTYSTRING 117 "fileName", // IDX_FILENAME 118 "lineNumber", // IDX_LINENUMBER 119 "columnNumber", // IDX_COLUMNNUMBER 120 "stack", // IDX_STACK 121 "message", // IDX_MESSAGE 122 "cause", // IDX_CAUSE 123 "errors", // IDX_ERRORS 124 "lastIndex", // IDX_LASTINDEX 125 "then", // IDX_THEN 126 "isInstance", // IDX_ISINSTANCE 127 "Infinity", // IDX_INFINITY 128 "NaN", // IDX_NAN 129 "classId", // IDX_CLASS_ID 130 "interfaceId", // IDX_INTERFACE_ID 131 "initializer", // IDX_INITIALIZER 132 "print", // IDX_PRINT 133 "fetch", // IDX_FETCH 134 "crypto", // IDX_CRYPTO 135 "indexedDB", // IDX_INDEXEDDB 136 "structuredClone", // IDX_STRUCTUREDCLONE 137 "locks", // IDX_LOCKS 138 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 139 "suppressed", // IDX_SUPPRESSED 140 "error", // IDX_ERROR 141 #endif 142 }; 143 144 /***************************************************************************/ 145 146 // *Some* NativeSets are referenced from mClassInfo2NativeSetMap. 147 // *All* NativeSets are referenced from mNativeSetMap. 148 // So, in mClassInfo2NativeSetMap we just clear references to the unmarked. 149 // In mNativeSetMap we clear the references to the unmarked *and* delete them. 150 151 class AsyncFreeSnowWhite : public Runnable { 152 public: 153 NS_IMETHOD Run() override { 154 AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC); 155 AUTO_PROFILER_LABEL("AsyncFreeSnowWhite::Run", GCCC_FreeSnowWhite); 156 157 auto timerId = glean::cycle_collector::async_snow_white_freeing.Start(); 158 // 2 ms budget, given that kICCSliceBudget is only 3 ms 159 SliceBudget budget = SliceBudget(TimeBudget(2)); 160 bool hadSnowWhiteObjects = 161 nsCycleCollector_doDeferredDeletionWithBudget(budget); 162 glean::cycle_collector::async_snow_white_freeing.StopAndAccumulate( 163 std::move(timerId)); 164 if (hadSnowWhiteObjects && !mContinuation) { 165 mContinuation = true; 166 if (NS_FAILED(Dispatch())) { 167 mActive = false; 168 } 169 } else { 170 mActive = false; 171 } 172 return NS_OK; 173 } 174 175 nsresult Dispatch() { 176 nsCOMPtr<nsIRunnable> self(this); 177 return NS_DispatchToCurrentThreadQueue(self.forget(), 1000, 178 EventQueuePriority::Idle); 179 } 180 181 void Start(bool aContinuation = false, bool aPurge = false) { 182 if (mContinuation) { 183 mContinuation = aContinuation; 184 } 185 mPurge = aPurge; 186 if (!mActive && NS_SUCCEEDED(Dispatch())) { 187 mActive = true; 188 } 189 } 190 191 AsyncFreeSnowWhite() 192 : Runnable("AsyncFreeSnowWhite"), 193 mContinuation(false), 194 mActive(false), 195 mPurge(false) {} 196 197 public: 198 bool mContinuation; 199 bool mActive; 200 bool mPurge; 201 }; 202 203 namespace xpc { 204 205 CompartmentPrivate::CompartmentPrivate( 206 JS::Compartment* c, mozilla::UniquePtr<XPCWrappedNativeScope> scope, 207 mozilla::BasePrincipal* origin, const SiteIdentifier& site) 208 : originInfo(origin, site), 209 wantXrays(false), 210 allowWaivers(true), 211 isWebExtensionContentScript(false), 212 isUAWidgetCompartment(false), 213 hasExclusiveExpandos(false), 214 wasShutdown(false), 215 mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()), 216 mScope(std::move(scope)) { 217 MOZ_COUNT_CTOR(xpc::CompartmentPrivate); 218 } 219 220 CompartmentPrivate::~CompartmentPrivate() { 221 MOZ_COUNT_DTOR(xpc::CompartmentPrivate); 222 } 223 224 void CompartmentPrivate::SystemIsBeingShutDown() { 225 // We may call this multiple times when the compartment contains more than one 226 // realm. 227 if (!wasShutdown) { 228 mWrappedJSMap->ShutdownMarker(); 229 wasShutdown = true; 230 } 231 } 232 233 RealmPrivate::RealmPrivate(JS::Realm* realm) : scriptability(realm) { 234 mozilla::PodArrayZero(wrapperDenialWarnings); 235 } 236 237 /* static */ 238 void RealmPrivate::Init(HandleObject aGlobal, const SiteIdentifier& aSite) { 239 MOZ_ASSERT(aGlobal); 240 DebugOnly<const JSClass*> clasp = JS::GetClass(aGlobal); 241 MOZ_ASSERT(clasp->slot0IsISupports() || dom::IsDOMClass(clasp)); 242 243 Realm* realm = GetObjectRealmOrNull(aGlobal); 244 245 // Create the realm private. 246 RealmPrivate* realmPriv = new RealmPrivate(realm); 247 MOZ_ASSERT(!GetRealmPrivate(realm)); 248 SetRealmPrivate(realm, realmPriv); 249 250 nsIPrincipal* principal = GetRealmPrincipal(realm); 251 Compartment* c = JS::GetCompartment(aGlobal); 252 253 // Create the compartment private if needed. 254 if (CompartmentPrivate* priv = CompartmentPrivate::Get(c)) { 255 MOZ_ASSERT(priv->originInfo.IsSameOrigin(principal)); 256 } else { 257 auto scope = mozilla::MakeUnique<XPCWrappedNativeScope>(c, aGlobal); 258 priv = new CompartmentPrivate(c, std::move(scope), 259 BasePrincipal::Cast(principal), aSite); 260 JS_SetCompartmentPrivate(c, priv); 261 } 262 } 263 264 // As XPCJSRuntime can live longer than when we shutdown the observer service, 265 // we have our own getter to account for this. 266 static nsCOMPtr<nsIObserverService> GetObserverService() { 267 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { 268 return nullptr; 269 } 270 return mozilla::services::GetObserverService(); 271 } 272 273 static bool TryParseLocationURICandidate( 274 const nsACString& uristr, RealmPrivate::LocationHint aLocationHint, 275 nsIURI** aURI) { 276 static constexpr auto kGRE = "resource://gre/"_ns; 277 static constexpr auto kToolkit = "chrome://global/"_ns; 278 static constexpr auto kBrowser = "chrome://browser/"_ns; 279 280 if (aLocationHint == RealmPrivate::LocationHintAddon) { 281 // Blacklist some known locations which are clearly not add-on related. 282 if (StringBeginsWith(uristr, kGRE) || StringBeginsWith(uristr, kToolkit) || 283 StringBeginsWith(uristr, kBrowser)) { 284 return false; 285 } 286 287 // -- GROSS HACK ALERT -- 288 // The Yandex Elements 8.10.2 extension implements its own "xb://" URL 289 // scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up 290 // calling into the extension's own JS-implemented nsIProtocolHandler 291 // object, which we can't allow while we're iterating over the JS heap. 292 // So just skip any such URL. 293 // -- GROSS HACK ALERT -- 294 if (StringBeginsWith(uristr, "xb"_ns)) { 295 return false; 296 } 297 } 298 299 nsCOMPtr<nsIURI> uri; 300 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) { 301 return false; 302 } 303 304 nsAutoCString scheme; 305 if (NS_FAILED(uri->GetScheme(scheme))) { 306 return false; 307 } 308 309 // Cannot really map data: and blob:. 310 // Also, data: URIs are pretty memory hungry, which is kinda bad 311 // for memory reporter use. 312 if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) { 313 return false; 314 } 315 316 uri.forget(aURI); 317 return true; 318 } 319 320 bool RealmPrivate::TryParseLocationURI(RealmPrivate::LocationHint aLocationHint, 321 nsIURI** aURI) { 322 if (!aURI) { 323 return false; 324 } 325 326 // Need to parse the URI. 327 if (location.IsEmpty()) { 328 return false; 329 } 330 331 // Handle Sandbox location strings. 332 // A sandbox string looks like this, for anonymous sandboxes, and builds 333 // where Sandbox location tagging is enabled: 334 // 335 // <sandboxName> (from: <js-stack-frame-filename>:<lineno>) 336 // 337 // where <sandboxName> is user-provided via Cu.Sandbox() 338 // and <js-stack-frame-filename> and <lineno> is the stack frame location 339 // from where Cu.Sandbox was called. 340 // 341 // Otherwise, it is simply the caller-provided name, which is usually a URI. 342 // 343 // <js-stack-frame-filename> furthermore is "free form", often using a 344 // "uri -> uri -> ..." chain. The following code will and must handle this 345 // common case. 346 // 347 // It should be noted that other parts of the code may already rely on the 348 // "format" of these strings. 349 350 static const nsDependentCString from("(from: "); 351 static const nsDependentCString arrow(" -> "); 352 static const size_t fromLength = from.Length(); 353 static const size_t arrowLength = arrow.Length(); 354 355 // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName 356 int32_t idx = location.Find(from); 357 if (idx < 0) { 358 return TryParseLocationURICandidate(location, aLocationHint, aURI); 359 } 360 361 // When parsing we're looking for the right-most URI. This URI may be in 362 // <sandboxName>, so we try this first. 363 if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint, 364 aURI)) { 365 return true; 366 } 367 368 // Not in <sandboxName> so we need to inspect <js-stack-frame-filename> and 369 // the chain that is potentially contained within and grab the rightmost 370 // item that is actually a URI. 371 372 // First, hack off the :<lineno>) part as well 373 int32_t ridx = location.RFind(":"_ns); 374 nsAutoCString chain( 375 Substring(location, idx + fromLength, ridx - idx - fromLength)); 376 377 // Loop over the "->" chain. This loop also works for non-chains, or more 378 // correctly chains with only one item. 379 for (;;) { 380 idx = chain.RFind(arrow); 381 if (idx < 0) { 382 // This is the last chain item. Try to parse what is left. 383 return TryParseLocationURICandidate(chain, aLocationHint, aURI); 384 } 385 386 // Try to parse current chain item 387 if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength), 388 aLocationHint, aURI)) { 389 return true; 390 } 391 392 // Current chain item couldn't be parsed. 393 // Strip current item and continue. 394 chain = Substring(chain, 0, idx); 395 } 396 397 MOZ_CRASH("Chain parser loop does not terminate"); 398 } 399 400 static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) { 401 // System principal gets a free pass. 402 if (aPrincipal->IsSystemPrincipal()) { 403 return true; 404 } 405 406 auto* principal = BasePrincipal::Cast(aPrincipal); 407 408 // ExpandedPrincipal gets a free pass. 409 if (principal->Is<ExpandedPrincipal>()) { 410 return true; 411 } 412 413 // WebExtension principals get a free pass. 414 if (principal->AddonPolicy()) { 415 return true; 416 } 417 418 // pdf.js is a special-case too. 419 if (nsContentUtils::IsPDFJS(principal)) { 420 return true; 421 } 422 423 // Check whether our URI is an "about:" URI that allows scripts. If it is, 424 // we need to allow JS to run. 425 if (aPrincipal->SchemeIs("about")) { 426 uint32_t flags; 427 nsresult rv = aPrincipal->GetAboutModuleFlags(&flags); 428 if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) { 429 return true; 430 } 431 } 432 433 return false; 434 } 435 436 void RealmPrivate::RegisterStackFrame(JSStackFrameBase* aFrame) { 437 mJSStackFrames.PutEntry(aFrame); 438 } 439 440 void RealmPrivate::UnregisterStackFrame(JSStackFrameBase* aFrame) { 441 mJSStackFrames.RemoveEntry(aFrame); 442 } 443 444 void RealmPrivate::NukeJSStackFrames() { 445 for (const auto& key : mJSStackFrames.Keys()) { 446 key->Clear(); 447 } 448 449 mJSStackFrames.Clear(); 450 } 451 452 void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) { 453 RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); 454 if (!realmPrivate) { 455 return; 456 } 457 458 realmPrivate->RegisterStackFrame(aStackFrame); 459 } 460 461 void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) { 462 RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); 463 if (!realmPrivate) { 464 return; 465 } 466 467 realmPrivate->UnregisterStackFrame(aStackFrame); 468 } 469 470 void NukeJSStackFrames(JS::Realm* aRealm) { 471 RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); 472 if (!realmPrivate) { 473 return; 474 } 475 476 realmPrivate->NukeJSStackFrames(); 477 } 478 479 Scriptability::Scriptability(JS::Realm* realm) 480 : mScriptBlocks(0), 481 mWindowAllowsScript(true), 482 mScriptBlockedByPolicy(false) { 483 nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm)); 484 485 mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); 486 if (mImmuneToScriptPolicy) { 487 return; 488 } 489 // If we're not immune, we should have a real principal with a URI. 490 // Check the principal against the new-style domain policy. 491 bool policyAllows; 492 nsresult rv = prin->GetIsScriptAllowedByPolicy(&policyAllows); 493 if (NS_SUCCEEDED(rv)) { 494 mScriptBlockedByPolicy = !policyAllows; 495 return; 496 } 497 // Something went wrong - be safe and block script. 498 mScriptBlockedByPolicy = true; 499 } 500 501 bool Scriptability::Allowed() { 502 return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0; 503 } 504 505 bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; } 506 507 void Scriptability::Block() { ++mScriptBlocks; } 508 509 void Scriptability::Unblock() { 510 MOZ_ASSERT(mScriptBlocks > 0); 511 --mScriptBlocks; 512 } 513 514 void Scriptability::SetWindowAllowsScript(bool aAllowed) { 515 mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy; 516 } 517 518 /* static */ 519 bool Scriptability::AllowedIfExists(JSObject* aScope) { 520 RealmPrivate* realmPrivate = RealmPrivate::Get(aScope); 521 return realmPrivate ? realmPrivate->scriptability.Allowed() : true; 522 } 523 524 /* static */ 525 Scriptability& Scriptability::Get(JSObject* aScope) { 526 return RealmPrivate::Get(aScope)->scriptability; 527 } 528 529 bool IsUAWidgetCompartment(JS::Compartment* compartment) { 530 // We always eagerly create compartment privates for UA Widget compartments. 531 CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); 532 return priv && priv->isUAWidgetCompartment; 533 } 534 535 bool IsUAWidgetScope(JS::Realm* realm) { 536 return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm)); 537 } 538 539 bool IsInUAWidgetScope(JSObject* obj) { 540 return IsUAWidgetCompartment(JS::GetCompartment(obj)); 541 } 542 543 bool CompartmentOriginInfo::MightBeWebContent() const { 544 // Compartments with principals that are either the system principal or an 545 // expanded principal are definitely not web content. 546 return !nsContentUtils::IsSystemOrExpandedPrincipal(mOrigin); 547 } 548 549 bool MightBeWebContentCompartment(JS::Compartment* compartment) { 550 if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) { 551 return priv->originInfo.MightBeWebContent(); 552 } 553 554 // No CompartmentPrivate; try IsSystemCompartment. 555 return !js::IsSystemCompartment(compartment); 556 } 557 558 bool CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const { 559 return mOrigin->FastEquals(aOther); 560 } 561 562 /* static */ 563 bool CompartmentOriginInfo::Subsumes(JS::Compartment* aCompA, 564 JS::Compartment* aCompB) { 565 CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA); 566 CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB); 567 MOZ_ASSERT(apriv); 568 MOZ_ASSERT(bpriv); 569 return apriv->originInfo.mOrigin->FastSubsumes(bpriv->originInfo.mOrigin); 570 } 571 572 /* static */ 573 bool CompartmentOriginInfo::SubsumesIgnoringFPD(JS::Compartment* aCompA, 574 JS::Compartment* aCompB) { 575 CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA); 576 CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB); 577 MOZ_ASSERT(apriv); 578 MOZ_ASSERT(bpriv); 579 return apriv->originInfo.mOrigin->FastSubsumesIgnoringFPD( 580 bpriv->originInfo.mOrigin); 581 } 582 583 void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment) { 584 // Note: we call this for all compartments that contain realms with a 585 // particular principal. Not all of these compartments have a 586 // CompartmentPrivate (for instance the temporary compartment/realm 587 // created by the JS engine for off-thread parsing). 588 if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) { 589 priv->originInfo.SetChangedDocumentDomain(); 590 } 591 } 592 593 JSObject* UnprivilegedJunkScope() { 594 return XPCJSRuntime::Get()->UnprivilegedJunkScope(); 595 } 596 597 JSObject* UnprivilegedJunkScope(const fallible_t&) { 598 return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible); 599 } 600 601 bool IsUnprivilegedJunkScope(JSObject* obj) { 602 return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj); 603 } 604 605 JSObject* NACScope(JSObject* global) { 606 // If we're a chrome global, just use ourselves. 607 if (AccessCheck::isChrome(global)) { 608 return global; 609 } 610 611 JSObject* scope = UnprivilegedJunkScope(); 612 JS::ExposeObjectToActiveJS(scope); 613 return scope; 614 } 615 616 JSObject* PrivilegedJunkScope() { 617 return mozJSModuleLoader::Get()->GetSharedGlobal(); 618 } 619 620 JSObject* CompilationScope() { 621 return mozJSModuleLoader::Get()->GetSharedGlobal(); 622 } 623 624 nsGlobalWindowInner* WindowOrNull(JSObject* aObj) { 625 MOZ_ASSERT(aObj); 626 MOZ_ASSERT(!js::IsWrapper(aObj)); 627 628 nsGlobalWindowInner* win = nullptr; 629 UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win); 630 return win; 631 } 632 633 nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj) { 634 MOZ_ASSERT(aObj); 635 JSObject* glob = JS::GetNonCCWObjectGlobal(aObj); 636 637 return WindowOrNull(glob); 638 } 639 640 JSObject* SandboxPrototypeOrNull(JSContext* aCx, JSObject* aObj) { 641 MOZ_ASSERT(aObj); 642 643 if (!IsSandbox(aObj)) { 644 return nullptr; 645 } 646 647 // Sandbox can't be a Proxy so it must have a static prototype. 648 JSObject* proto = GetStaticPrototype(aObj); 649 if (!proto || !IsSandboxPrototypeProxy(proto)) { 650 return nullptr; 651 } 652 653 return js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false); 654 } 655 656 nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) { 657 JSObject* glob = JS::CurrentGlobalOrNull(cx); 658 return glob ? WindowOrNull(glob) : nullptr; 659 } 660 661 // Nukes all wrappers into or out of the given realm, and prevents new 662 // wrappers from being created. Additionally marks the realm as 663 // unscriptable after wrappers have been nuked. 664 // 665 // Note: This should *only* be called for browser or extension realms. 666 // Wrappers between web compartments must never be cut in web-observable 667 // ways. 668 void NukeAllWrappersForRealm( 669 JSContext* cx, JS::Realm* realm, 670 js::NukeReferencesToWindow nukeReferencesToWindow) { 671 // We do the following: 672 // * Nuke all wrappers into the realm. 673 // * Nuke all wrappers out of the realm's compartment, once we have nuked all 674 // realms in it. 675 js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm, 676 nukeReferencesToWindow, 677 js::NukeAllReferences); 678 679 // Mark the realm as unscriptable. 680 xpc::RealmPrivate::Get(realm)->scriptability.Block(); 681 } 682 683 } // namespace xpc 684 685 static void CompartmentDestroyedCallback(JS::GCContext* gcx, 686 JS::Compartment* compartment) { 687 // NB - This callback may be called in JS_DestroyContext, which happens 688 // after the XPCJSRuntime has been torn down. 689 690 // Get the current compartment private into a UniquePtr (which will do the 691 // cleanup for us), and null out the private (which may already be null). 692 mozilla::UniquePtr<CompartmentPrivate> priv( 693 CompartmentPrivate::Get(compartment)); 694 JS_SetCompartmentPrivate(compartment, nullptr); 695 } 696 697 static size_t CompartmentSizeOfIncludingThisCallback( 698 MallocSizeOf mallocSizeOf, JS::Compartment* compartment) { 699 CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); 700 return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0; 701 } 702 703 /* 704 * Return true if there exists a non-system inner window which is a current 705 * inner window and whose reflector is gray. We don't merge system 706 * compartments, so we don't use them to trigger merging CCs. 707 */ 708 bool XPCJSRuntime::UsefulToMergeZones() const { 709 MOZ_ASSERT(NS_IsMainThread()); 710 711 // Turns out, actually making this return true often enough makes Windows 712 // mochitest-gl OOM a lot. Need to figure out what's going on there; see 713 // bug 1277036. 714 715 return false; 716 } 717 718 void XPCJSRuntime::TraceAdditionalNativeBlackRoots(JSTracer* trc) { 719 if (CycleCollectedJSContext* ccx = GetContext()) { 720 const auto* cx = static_cast<const XPCJSContext*>(ccx); 721 if (AutoMarkingPtr* roots = cx->mAutoRoots) { 722 roots->TraceJSAll(trc); 723 } 724 } 725 726 if (mIID2NativeInterfaceMap) { 727 mIID2NativeInterfaceMap->Trace(trc); 728 } 729 730 dom::TraceBlackJS(trc); 731 } 732 733 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) { 734 XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc); 735 } 736 737 void XPCJSRuntime::TraverseAdditionalNativeRoots( 738 nsCycleCollectionNoteRootCallback& cb) { 739 XPCWrappedNativeScope::SuspectAllWrappers(cb); 740 741 auto* parti = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS); 742 for (auto* wjs : mSubjectToFinalizationWJS) { 743 MOZ_DIAGNOSTIC_ASSERT(wjs->IsSubjectToFinalization()); 744 cb.NoteXPCOMRoot(ToSupports(wjs), parti); 745 } 746 } 747 748 void XPCJSRuntime::UnmarkSkippableJSHolders() { 749 CycleCollectedJSRuntime::UnmarkSkippableJSHolders(); 750 } 751 752 void XPCJSRuntime::PrepareForForgetSkippable() { 753 nsCCUncollectableMarker::CleanupForForgetSkippable(); 754 } 755 756 void XPCJSRuntime::BeginCycleCollectionCallback(CCReason aReason) { 757 nsJSContext::BeginCycleCollectionCallback(aReason); 758 759 nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); 760 if (obs) { 761 obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr); 762 } 763 } 764 765 void XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults& aResults) { 766 nsJSContext::EndCycleCollectionCallback(aResults); 767 768 nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); 769 if (obs) { 770 obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr); 771 } 772 } 773 774 void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation, bool aPurge) { 775 mAsyncSnowWhiteFreer->Start(aContinuation, aPurge); 776 } 777 778 void xpc_UnmarkSkippableJSHolders() { 779 if (nsXPConnect::GetRuntimeInstance()) { 780 nsXPConnect::GetRuntimeInstance()->UnmarkSkippableJSHolders(); 781 } 782 } 783 784 /* static */ 785 void XPCJSRuntime::GCSliceCallback(JSContext* cx, JS::GCProgress progress, 786 const JS::GCDescription& desc) { 787 XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); 788 if (!self) { 789 return; 790 } 791 792 nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); 793 if (obs) { 794 switch (progress) { 795 case JS::GC_CYCLE_BEGIN: 796 obs->NotifyObservers(nullptr, "garbage-collector-begin", nullptr); 797 break; 798 case JS::GC_CYCLE_END: 799 obs->NotifyObservers(nullptr, "garbage-collector-end", nullptr); 800 break; 801 default: 802 break; 803 } 804 } 805 806 CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN); 807 808 if (self->mPrevGCSliceCallback) { 809 (*self->mPrevGCSliceCallback)(cx, progress, desc); 810 } 811 } 812 813 /* static */ 814 void XPCJSRuntime::DoCycleCollectionCallback(JSContext* cx) { 815 // The GC has detected that a CC at this point would collect a tremendous 816 // amount of garbage that is being revivified unnecessarily. 817 // 818 // The GC_WAITING reason is a little overloaded here, but we want to do 819 // a CC to allow Realms to be collected when they are referenced by a cycle. 820 NS_DispatchToCurrentThread(NS_NewRunnableFunction( 821 "XPCJSRuntime::DoCycleCollectionCallback", 822 []() { nsJSContext::CycleCollectNow(CCReason::GC_WAITING, nullptr); })); 823 824 XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); 825 if (!self) { 826 return; 827 } 828 829 if (self->mPrevDoCycleCollectionCallback) { 830 (*self->mPrevDoCycleCollectionCallback)(cx); 831 } 832 } 833 834 void XPCJSRuntime::CustomGCCallback(JSGCStatus status) { 835 nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone()); 836 for (uint32_t i = 0; i < callbacks.Length(); ++i) { 837 callbacks[i](status); 838 } 839 } 840 841 /* static */ 842 void XPCJSRuntime::FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status, 843 void* data) { 844 XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); 845 if (!self) { 846 return; 847 } 848 849 switch (status) { 850 case JSFINALIZE_GROUP_PREPARE: { 851 MOZ_ASSERT(!self->mDoingFinalization, "bad state"); 852 853 MOZ_ASSERT(!self->mGCIsRunning, "bad state"); 854 self->mGCIsRunning = true; 855 856 self->mDoingFinalization = true; 857 858 break; 859 } 860 case JSFINALIZE_GROUP_START: { 861 MOZ_ASSERT(self->mDoingFinalization, "bad state"); 862 863 MOZ_ASSERT(self->mGCIsRunning, "bad state"); 864 self->mGCIsRunning = false; 865 866 break; 867 } 868 case JSFINALIZE_GROUP_END: { 869 MOZ_ASSERT(self->mDoingFinalization, "bad state"); 870 self->mDoingFinalization = false; 871 872 break; 873 } 874 case JSFINALIZE_COLLECTION_END: { 875 MOZ_ASSERT(!self->mGCIsRunning, "bad state"); 876 self->mGCIsRunning = true; 877 878 if (CycleCollectedJSContext* ccx = self->GetContext()) { 879 const auto* cx = static_cast<const XPCJSContext*>(ccx); 880 if (AutoMarkingPtr* roots = cx->mAutoRoots) { 881 roots->MarkAfterJSFinalizeAll(); 882 } 883 884 // Now we are going to recycle any unused WrappedNativeTearoffs. 885 // We do this by iterating all the live callcontexts 886 // and marking the tearoffs in use. And then we 887 // iterate over all the WrappedNative wrappers and sweep their 888 // tearoffs. 889 // 890 // This allows us to perhaps minimize the growth of the 891 // tearoffs. And also makes us not hold references to interfaces 892 // on our wrapped natives that we are not actually using. 893 // 894 // XXX We may decide to not do this on *every* gc cycle. 895 896 XPCCallContext* ccxp = cx->GetCallContext(); 897 while (ccxp) { 898 // Deal with the strictness of callcontext that 899 // complains if you ask for a tearoff when 900 // it is in a state where the tearoff could not 901 // possibly be valid. 902 if (ccxp->CanGetTearOff()) { 903 XPCWrappedNativeTearOff* to = ccxp->GetTearOff(); 904 if (to) { 905 to->Mark(); 906 } 907 } 908 ccxp = ccxp->GetPrevCallContext(); 909 } 910 } 911 912 XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs(); 913 914 // Now we need to kill the 'Dying' XPCWrappedNativeProtos. 915 // 916 // We transferred these native objects to this list when their JSObjects 917 // were finalized. We did not destroy them immediately at that point 918 // because the ordering of JS finalization is not deterministic and we did 919 // not yet know if any wrappers that might still be referencing the protos 920 // were still yet to be finalized and destroyed. We *do* know that the 921 // protos' JSObjects would not have been finalized if there were any 922 // wrappers that referenced the proto but were not themselves slated for 923 // finalization in this gc cycle. 924 // 925 // At this point we know that any and all wrappers that might have been 926 // referencing the protos in the dying list are themselves dead. So, we 927 // can safely delete all the protos in the list. 928 self->mDyingWrappedNativeProtos.clear(); 929 930 MOZ_ASSERT(self->mGCIsRunning, "bad state"); 931 self->mGCIsRunning = false; 932 933 break; 934 } 935 } 936 } 937 938 /* static */ 939 void XPCJSRuntime::WeakPointerZonesCallback(JSTracer* trc, void* data) { 940 // Called before each sweeping slice -- after processing any final marking 941 // triggered by barriers -- to clear out any references to things that are 942 // about to be finalized and update any pointers to moved GC things. 943 XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data); 944 945 // This callback is always called from within the GC so set the mGCIsRunning 946 // flag to prevent AssertInvalidWrappedJSNotInTable from trying to call back 947 // into the JS API. This has often already been set by FinalizeCallback by the 948 // time we get here, but it may not be if we are doing a shutdown GC or if we 949 // are called for compacting GC. 950 AutoRestore<bool> restoreState(self->mGCIsRunning); 951 self->mGCIsRunning = true; 952 953 self->mWrappedJSMap->UpdateWeakPointersAfterGC(trc); 954 self->mUAWidgetScopeMap.traceWeak(trc); 955 } 956 957 /* static */ 958 void XPCJSRuntime::WeakPointerCompartmentCallback(JSTracer* trc, 959 JS::Compartment* comp, 960 void* data) { 961 // Called immediately after the ZoneGroup weak pointer callback, but only 962 // once for each compartment that is being swept. 963 CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp); 964 if (xpcComp) { 965 xpcComp->UpdateWeakPointersAfterGC(trc); 966 } 967 } 968 969 void CompartmentPrivate::UpdateWeakPointersAfterGC(JSTracer* trc) { 970 mRemoteProxies.traceWeak(trc); 971 mWrappedJSMap->UpdateWeakPointersAfterGC(trc); 972 mScope->UpdateWeakPointersAfterGC(trc); 973 } 974 975 void XPCJSRuntime::CustomOutOfMemoryCallback() { 976 if (!Preferences::GetBool("memory.dump_reports_on_oom")) { 977 return; 978 } 979 980 nsCOMPtr<nsIMemoryInfoDumper> dumper = 981 do_GetService("@mozilla.org/memory-info-dumper;1"); 982 if (!dumper) { 983 return; 984 } 985 986 // If this fails, it fails silently. 987 dumper->DumpMemoryInfoToTempDir(u"due-to-JS-OOM"_ns, 988 /* anonymize = */ false, 989 /* minimizeMemoryUsage = */ false); 990 } 991 992 void XPCJSRuntime::OnLargeAllocationFailure() { 993 CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reporting); 994 995 nsCOMPtr<nsIObserverService> os = xpc::GetObserverService(); 996 if (os) { 997 os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); 998 } 999 1000 CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reported); 1001 } 1002 1003 class LargeAllocationFailureRunnable final : public Runnable { 1004 Mutex mMutex MOZ_UNANNOTATED; 1005 CondVar mCondVar; 1006 bool mWaiting; 1007 1008 virtual ~LargeAllocationFailureRunnable() { MOZ_ASSERT(!mWaiting); } 1009 1010 protected: 1011 NS_IMETHOD Run() override { 1012 MOZ_ASSERT(NS_IsMainThread()); 1013 1014 XPCJSRuntime::Get()->OnLargeAllocationFailure(); 1015 1016 MutexAutoLock lock(mMutex); 1017 MOZ_ASSERT(mWaiting); 1018 1019 mWaiting = false; 1020 mCondVar.Notify(); 1021 return NS_OK; 1022 } 1023 1024 public: 1025 LargeAllocationFailureRunnable() 1026 : mozilla::Runnable("LargeAllocationFailureRunnable"), 1027 mMutex("LargeAllocationFailureRunnable::mMutex"), 1028 mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"), 1029 mWaiting(true) { 1030 MOZ_ASSERT(!NS_IsMainThread()); 1031 } 1032 1033 void BlockUntilDone() { 1034 MOZ_ASSERT(!NS_IsMainThread()); 1035 1036 MutexAutoLock lock(mMutex); 1037 while (mWaiting) { 1038 mCondVar.Wait(); 1039 } 1040 } 1041 }; 1042 1043 static void OnLargeAllocationFailureCallback() { 1044 // This callback can be called from any thread, including internal JS helper 1045 // and DOM worker threads. We need to send the low-memory event via the 1046 // observer service which can only be called on the main thread, so proxy to 1047 // the main thread if we're not there already. The purpose of this callback 1048 // is to synchronously free some memory so the caller can retry a failed 1049 // allocation, so block on the completion. 1050 1051 if (NS_IsMainThread()) { 1052 XPCJSRuntime::Get()->OnLargeAllocationFailure(); 1053 return; 1054 } 1055 1056 RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable; 1057 if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) { 1058 return; 1059 } 1060 1061 r->BlockUntilDone(); 1062 } 1063 1064 // Usually this is used through nsIPlatformInfo. However, being able to query 1065 // this interface on all threads risk triggering some main-thread assertions 1066 // which is not guaranteed by the callers of GetBuildId. 1067 extern const char gToolkitBuildID[]; 1068 1069 bool mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) { 1070 size_t length = std::char_traits<char>::length(gToolkitBuildID); 1071 return aBuildID->append(gToolkitBuildID, length); 1072 } 1073 1074 size_t XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { 1075 size_t n = 0; 1076 n += mallocSizeOf(this); 1077 n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); 1078 n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf); 1079 n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf); 1080 n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf); 1081 1082 n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf); 1083 1084 // There are other XPCJSRuntime members that could be measured; the above 1085 // ones have been seen by DMD to be worth measuring. More stuff may be 1086 // added later. 1087 1088 return n; 1089 } 1090 1091 size_t CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { 1092 size_t n = mallocSizeOf(this); 1093 n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); 1094 n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf); 1095 return n; 1096 } 1097 1098 /***************************************************************************/ 1099 1100 void XPCJSRuntime::Shutdown(JSContext* cx) { 1101 // This destructor runs before ~CycleCollectedJSContext, which does the actual 1102 // JS_DestroyContext() call. But destroying the context triggers one final GC, 1103 // which can call back into the context with various callbacks if we aren't 1104 // careful. Remove the relevant callbacks, but leave the weak pointer 1105 // callbacks to clear out any remaining table entries. 1106 JS_RemoveFinalizeCallback(cx, FinalizeCallback); 1107 xpc_DelocalizeRuntime(JS_GetRuntime(cx)); 1108 1109 JS::SetGCSliceCallback(cx, mPrevGCSliceCallback); 1110 1111 nsScriptSecurityManager::ClearJSCallbacks(cx); 1112 1113 // Clean up and destroy maps. Any remaining entries in mWrappedJSMap will be 1114 // cleaned up by the weak pointer callbacks. 1115 mIID2NativeInterfaceMap = nullptr; 1116 1117 mClassInfo2NativeSetMap = nullptr; 1118 1119 mNativeSetMap = nullptr; 1120 1121 // Prevent ~LinkedList assertion failures if we leaked things. 1122 mWrappedNativeScopes.clear(); 1123 1124 mSubjectToFinalizationWJS.clear(); 1125 1126 CycleCollectedJSRuntime::Shutdown(cx); 1127 } 1128 1129 XPCJSRuntime::~XPCJSRuntime() { 1130 MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime); 1131 } 1132 1133 // If |*anonymizeID| is non-zero and this is a user realm, the name will 1134 // be anonymized. 1135 static void GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID, 1136 bool replaceSlashes) { 1137 if (*anonymizeID && !js::IsSystemRealm(realm)) { 1138 name.AppendPrintf("<anonymized-%d>", *anonymizeID); 1139 *anonymizeID += 1; 1140 } else if (JSPrincipals* principals = JS::GetRealmPrincipals(realm)) { 1141 nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name); 1142 if (NS_FAILED(rv)) { 1143 name.AssignLiteral("(unknown)"); 1144 } 1145 1146 // If the realm's location (name) differs from the principal's script 1147 // location, append the realm's location to allow differentiation of 1148 // multiple realms owned by the same principal (e.g. components owned 1149 // by the system or null principal). 1150 RealmPrivate* realmPrivate = RealmPrivate::Get(realm); 1151 if (realmPrivate) { 1152 const nsACString& location = realmPrivate->GetLocation(); 1153 if (!location.IsEmpty() && !location.Equals(name)) { 1154 name.AppendLiteral(", "); 1155 name.Append(location); 1156 } 1157 } 1158 1159 if (*anonymizeID) { 1160 // We might have a file:// URL that includes a path from the local 1161 // filesystem, which should be omitted if we're anonymizing. 1162 static const char* filePrefix = "file://"; 1163 int filePos = name.Find(filePrefix); 1164 if (filePos >= 0) { 1165 int pathPos = filePos + strlen(filePrefix); 1166 int lastSlashPos = -1; 1167 for (int i = pathPos; i < int(name.Length()); i++) { 1168 if (name[i] == '/' || name[i] == '\\') { 1169 lastSlashPos = i; 1170 } 1171 } 1172 if (lastSlashPos != -1) { 1173 name.ReplaceLiteral(pathPos, lastSlashPos - pathPos, "<anonymized>"); 1174 } else { 1175 // Something went wrong. Anonymize the entire path to be 1176 // safe. 1177 name.Truncate(pathPos); 1178 name += "<anonymized?!>"; 1179 } 1180 } 1181 1182 // We might have a location like this: 1183 // inProcessBrowserChildGlobal?ownedBy=http://www.example.com/ 1184 // The owner should be omitted if it's not a chrome: URI and we're 1185 // anonymizing. 1186 static const char* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy="; 1187 int ownedByPos = name.Find(ownedByPrefix); 1188 if (ownedByPos >= 0) { 1189 const char* chrome = "chrome:"; 1190 int ownerPos = ownedByPos + strlen(ownedByPrefix); 1191 const nsDependentCSubstring& ownerFirstPart = 1192 Substring(name, ownerPos, strlen(chrome)); 1193 if (!ownerFirstPart.EqualsASCII(chrome)) { 1194 name.Truncate(ownerPos); 1195 name += "<anonymized>"; 1196 } 1197 } 1198 } 1199 1200 // A hack: replace forward slashes with '\\' so they aren't 1201 // treated as path separators. Users of the reporters 1202 // (such as about:memory) have to undo this change. 1203 if (replaceSlashes) { 1204 name.ReplaceChar('/', '\\'); 1205 } 1206 } else { 1207 name.AssignLiteral("null-principal"); 1208 } 1209 } 1210 1211 extern void xpc::GetCurrentRealmName(JSContext* cx, nsCString& name) { 1212 RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); 1213 if (!global) { 1214 name.AssignLiteral("no global"); 1215 return; 1216 } 1217 1218 JS::Realm* realm = GetNonCCWObjectRealm(global); 1219 int anonymizeID = 0; 1220 GetRealmName(realm, name, &anonymizeID, false); 1221 } 1222 1223 void xpc::AddGCCallback(xpcGCCallback cb) { 1224 XPCJSRuntime::Get()->AddGCCallback(cb); 1225 } 1226 1227 void xpc::RemoveGCCallback(xpcGCCallback cb) { 1228 XPCJSRuntime::Get()->RemoveGCCallback(cb); 1229 } 1230 1231 static int64_t JSMainRuntimeGCHeapDistinguishedAmount() { 1232 JSContext* cx = danger::GetJSContext(); 1233 return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize; 1234 } 1235 1236 static int64_t JSMainRuntimeTemporaryPeakDistinguishedAmount() { 1237 JSContext* cx = danger::GetJSContext(); 1238 return JS::PeakSizeOfTemporary(cx); 1239 } 1240 1241 static int64_t JSMainRuntimeCompartmentsSystemDistinguishedAmount() { 1242 JSContext* cx = danger::GetJSContext(); 1243 return JS::SystemCompartmentCount(cx); 1244 } 1245 1246 static int64_t JSMainRuntimeCompartmentsUserDistinguishedAmount() { 1247 JSContext* cx = XPCJSContext::Get()->Context(); 1248 return JS::UserCompartmentCount(cx); 1249 } 1250 1251 static int64_t JSMainRuntimeRealmsSystemDistinguishedAmount() { 1252 JSContext* cx = danger::GetJSContext(); 1253 return JS::SystemRealmCount(cx); 1254 } 1255 1256 static int64_t JSMainRuntimeRealmsUserDistinguishedAmount() { 1257 JSContext* cx = XPCJSContext::Get()->Context(); 1258 return JS::UserRealmCount(cx); 1259 } 1260 1261 class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter { 1262 ~JSMainRuntimeTemporaryPeakReporter() = default; 1263 1264 public: 1265 NS_DECL_ISUPPORTS 1266 1267 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, 1268 nsISupports* aData, bool aAnonymize) override { 1269 MOZ_COLLECT_REPORT( 1270 "js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES, 1271 JSMainRuntimeTemporaryPeakDistinguishedAmount(), 1272 "Peak transient data size in the main JSRuntime (the current size " 1273 "of which is reported as " 1274 "'explicit/js-non-window/runtime/temporary')."); 1275 1276 return NS_OK; 1277 } 1278 }; 1279 1280 NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter) 1281 1282 // The REPORT* macros do an unconditional report. The ZRREPORT* macros are for 1283 // realms and zones; they aggregate any entries smaller than 1284 // SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap" 1285 // entries for the realm. 1286 1287 #define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold() 1288 1289 #define REPORT(_path, _kind, _units, _amount, _desc) \ 1290 handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ 1291 nsIMemoryReporter::_units, _amount, \ 1292 nsLiteralCString(_desc), data); 1293 1294 #define REPORT_BYTES(_path, _kind, _amount, _desc) \ 1295 REPORT(_path, _kind, UNITS_BYTES, _amount, _desc); 1296 1297 #define REPORT_GC_BYTES(_path, _amount, _desc) \ 1298 do { \ 1299 size_t amount = _amount; /* evaluate _amount only once */ \ 1300 handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ 1301 nsIMemoryReporter::UNITS_BYTES, amount, \ 1302 nsLiteralCString(_desc), data); \ 1303 gcTotal += amount; \ 1304 } while (0) 1305 1306 // Report realm/zone non-GC (KIND_HEAP) bytes. 1307 #define ZRREPORT_BYTES(_path, _amount, _desc) \ 1308 do { \ 1309 /* Assign _descLiteral plus "" into a char* to prove that it's */ \ 1310 /* actually a literal. */ \ 1311 size_t amount = _amount; /* evaluate _amount only once */ \ 1312 if (amount >= SUNDRIES_THRESHOLD) { \ 1313 handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_HEAP, \ 1314 nsIMemoryReporter::UNITS_BYTES, amount, \ 1315 nsLiteralCString(_desc), data); \ 1316 } else { \ 1317 sundriesMallocHeap += amount; \ 1318 } \ 1319 } while (0) 1320 1321 // Report realm/zone GC bytes. 1322 #define ZRREPORT_GC_BYTES(_path, _amount, _desc) \ 1323 do { \ 1324 size_t amount = _amount; /* evaluate _amount only once */ \ 1325 if (amount >= SUNDRIES_THRESHOLD) { \ 1326 handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ 1327 nsIMemoryReporter::UNITS_BYTES, amount, \ 1328 nsLiteralCString(_desc), data); \ 1329 gcTotal += amount; \ 1330 } else { \ 1331 sundriesGCHeap += amount; \ 1332 } \ 1333 } while (0) 1334 1335 // Report realm/zone non-heap bytes. 1336 #define ZRREPORT_NONHEAP_BYTES(_path, _amount, _desc) \ 1337 do { \ 1338 size_t amount = _amount; /* evaluate _amount only once */ \ 1339 if (amount >= SUNDRIES_THRESHOLD) { \ 1340 handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ 1341 nsIMemoryReporter::UNITS_BYTES, amount, \ 1342 nsLiteralCString(_desc), data); \ 1343 } else { \ 1344 sundriesNonHeap += amount; \ 1345 } \ 1346 } while (0) 1347 1348 // Report runtime bytes. 1349 #define RREPORT_BYTES(_path, _kind, _amount, _desc) \ 1350 do { \ 1351 size_t amount = _amount; /* evaluate _amount only once */ \ 1352 handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ 1353 nsIMemoryReporter::UNITS_BYTES, amount, \ 1354 nsLiteralCString(_desc), data); \ 1355 rtTotal += amount; \ 1356 } while (0) 1357 1358 // Report GC thing bytes. 1359 #define MREPORT_BYTES(_path, _kind, _amount, _desc) \ 1360 do { \ 1361 size_t amount = _amount; /* evaluate _amount only once */ \ 1362 handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ 1363 nsIMemoryReporter::UNITS_BYTES, amount, \ 1364 nsLiteralCString(_desc), data); \ 1365 gcThingTotal += amount; \ 1366 } while (0) 1367 1368 MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf) 1369 1370 namespace xpc { 1371 1372 static void ReportZoneStats(const JS::ZoneStats& zStats, 1373 const xpc::ZoneStatsExtras& extras, 1374 nsIHandleReportCallback* handleReport, 1375 nsISupports* data, bool anonymize, 1376 size_t* gcTotalOut = nullptr) { 1377 const nsCString& pathPrefix = extras.pathPrefix; 1378 size_t gcTotal = 0; 1379 size_t sundriesGCHeap = 0; 1380 size_t sundriesMallocHeap = 0; 1381 size_t sundriesNonHeap = 0; 1382 1383 MOZ_ASSERT(!gcTotalOut == zStats.isTotals); 1384 1385 ZRREPORT_GC_BYTES(pathPrefix + "symbols/gc-heap"_ns, zStats.symbolsGCHeap, 1386 "Symbols."); 1387 1388 ZRREPORT_GC_BYTES( 1389 pathPrefix + "gc-heap-arena-admin"_ns, zStats.gcHeapArenaAdmin, 1390 "Bookkeeping information and alignment padding within GC arenas."); 1391 1392 ZRREPORT_GC_BYTES(pathPrefix + "unused-gc-things"_ns, 1393 zStats.unusedGCThings.totalSize(), 1394 "Unused GC thing cells within non-empty arenas."); 1395 1396 ZRREPORT_BYTES(pathPrefix + "unique-id-map"_ns, zStats.uniqueIdMap, 1397 "Address-independent cell identities."); 1398 1399 ZRREPORT_BYTES(pathPrefix + "propmap-tables"_ns, zStats.initialPropMapTable, 1400 "Tables storing property map information."); 1401 1402 ZRREPORT_BYTES(pathPrefix + "shape-tables"_ns, zStats.shapeTables, 1403 "Tables storing shape information."); 1404 1405 ZRREPORT_BYTES(pathPrefix + "compartments/compartment-objects"_ns, 1406 zStats.compartmentObjects, 1407 "The JS::Compartment objects in this zone."); 1408 1409 ZRREPORT_BYTES( 1410 pathPrefix + "compartments/cross-compartment-wrapper-tables"_ns, 1411 zStats.crossCompartmentWrappersTables, 1412 "The cross-compartment wrapper tables."); 1413 1414 ZRREPORT_BYTES( 1415 pathPrefix + "compartments/private-data"_ns, 1416 zStats.compartmentsPrivateData, 1417 "Extra data attached to each compartment by XPConnect, including " 1418 "its wrapped-js."); 1419 1420 ZRREPORT_GC_BYTES(pathPrefix + "bigints/gc-heap"_ns, zStats.bigIntsGCHeap, 1421 "BigInt values."); 1422 1423 ZRREPORT_BYTES(pathPrefix + "bigints/malloc-heap"_ns, 1424 zStats.bigIntsMallocHeap, "BigInt values."); 1425 1426 ZRREPORT_GC_BYTES(pathPrefix + "jit-codes-gc-heap"_ns, zStats.jitCodesGCHeap, 1427 "References to executable code pools used by the JITs."); 1428 1429 ZRREPORT_GC_BYTES(pathPrefix + "getter-setters-gc-heap"_ns, 1430 zStats.getterSettersGCHeap, 1431 "Information for getter/setter properties."); 1432 1433 ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/compact"_ns, 1434 zStats.compactPropMapsGCHeap, 1435 "Information about object properties."); 1436 1437 ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/normal"_ns, 1438 zStats.normalPropMapsGCHeap, 1439 "Information about object properties."); 1440 1441 ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/dict"_ns, 1442 zStats.dictPropMapsGCHeap, 1443 "Information about dictionary mode object properties."); 1444 1445 ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/children"_ns, 1446 zStats.propMapChildren, "Tables for PropMap children."); 1447 1448 ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/tables"_ns, 1449 zStats.propMapTables, "HashTables for PropMaps."); 1450 1451 ZRREPORT_GC_BYTES(pathPrefix + "scopes/gc-heap"_ns, zStats.scopesGCHeap, 1452 "Scope information for scripts."); 1453 1454 ZRREPORT_BYTES(pathPrefix + "scopes/malloc-heap"_ns, zStats.scopesMallocHeap, 1455 "Arrays of binding names and other binding-related data."); 1456 1457 ZRREPORT_GC_BYTES(pathPrefix + "regexp-shareds/gc-heap"_ns, 1458 zStats.regExpSharedsGCHeap, "Shared compiled regexp data."); 1459 1460 ZRREPORT_BYTES(pathPrefix + "regexp-shareds/malloc-heap"_ns, 1461 zStats.regExpSharedsMallocHeap, 1462 "Shared compiled regexp data."); 1463 1464 ZRREPORT_BYTES(pathPrefix + "zone-object"_ns, zStats.zoneObject, 1465 "The JS::Zone object itself."); 1466 1467 ZRREPORT_BYTES(pathPrefix + "regexp-zone"_ns, zStats.regexpZone, 1468 "The regexp zone and regexp data."); 1469 1470 ZRREPORT_BYTES(pathPrefix + "jit-zone"_ns, zStats.jitZone, "The JIT zone."); 1471 1472 ZRREPORT_BYTES(pathPrefix + "cacheir-stubs"_ns, zStats.cacheIRStubs, 1473 "The JIT's IC stubs (excluding code)."); 1474 1475 ZRREPORT_BYTES(pathPrefix + "object-fuses"_ns, zStats.objectFuses, 1476 "Information about constant object properties."); 1477 1478 ZRREPORT_BYTES(pathPrefix + "script-counts-map"_ns, zStats.scriptCountsMap, 1479 "Profiling-related information for scripts."); 1480 1481 ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/ion"_ns, zStats.code.ion, 1482 "Code generated by the IonMonkey JIT."); 1483 1484 ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/baseline"_ns, zStats.code.baseline, 1485 "Code generated by the Baseline JIT."); 1486 1487 ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/regexp"_ns, zStats.code.regexp, 1488 "Code generated by the regexp JIT."); 1489 1490 ZRREPORT_NONHEAP_BYTES( 1491 pathPrefix + "code/other"_ns, zStats.code.other, 1492 "Code generated by the JITs for wrappers and trampolines."); 1493 1494 ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/unused"_ns, zStats.code.unused, 1495 "Memory allocated by one of the JITs to hold code, " 1496 "but which is currently unused."); 1497 1498 size_t stringsNotableAboutMemoryGCHeap = 0; 1499 size_t stringsNotableAboutMemoryMallocHeap = 0; 1500 1501 #define MAYBE_INLINE "The characters may be inline or on the malloc heap." 1502 #define MAYBE_OVERALLOCATED \ 1503 "Sometimes over-allocated to simplify string concatenation." 1504 1505 for (size_t i = 0; i < zStats.notableStrings.length(); i++) { 1506 const JS::NotableStringInfo& info = zStats.notableStrings[i]; 1507 1508 MOZ_ASSERT(!zStats.isTotals); 1509 1510 // We don't do notable string detection when anonymizing, because 1511 // there's a good chance its for crash submission, and the memory 1512 // required for notable string detection is high. 1513 MOZ_ASSERT(!anonymize); 1514 1515 nsDependentCString notableString(info.buffer.get()); 1516 1517 // Viewing about:memory generates many notable strings which contain 1518 // "string(length=". If we report these as notable, then we'll create 1519 // even more notable strings the next time we open about:memory (unless 1520 // there's a GC in the meantime), and so on ad infinitum. 1521 // 1522 // To avoid cluttering up about:memory like this, we stick notable 1523 // strings which contain "string(length=" into their own bucket. 1524 #define STRING_LENGTH "string(length=" 1525 if (FindInReadable(nsLiteralCString(STRING_LENGTH), notableString)) { 1526 stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1; 1527 stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte; 1528 stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1; 1529 stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte; 1530 continue; 1531 } 1532 1533 // Escape / to \ before we put notableString into the memory reporter 1534 // path, because we don't want any forward slashes in the string to 1535 // count as path separators. 1536 nsCString escapedString(notableString); 1537 escapedString.ReplaceSubstring("/", "\\"); 1538 1539 bool truncated = notableString.Length() < info.length; 1540 1541 nsCString path = 1542 pathPrefix + 1543 nsPrintfCString("strings/" STRING_LENGTH "%zu, copies=%d, \"%s\"%s)/", 1544 info.length, info.numCopies, escapedString.get(), 1545 truncated ? " (truncated)" : ""); 1546 1547 if (info.gcHeapLatin1 > 0) { 1548 REPORT_GC_BYTES(path + "gc-heap/latin1"_ns, info.gcHeapLatin1, 1549 "Latin1 strings. " MAYBE_INLINE); 1550 } 1551 1552 if (info.gcHeapTwoByte > 0) { 1553 REPORT_GC_BYTES(path + "gc-heap/two-byte"_ns, info.gcHeapTwoByte, 1554 "TwoByte strings. " MAYBE_INLINE); 1555 } 1556 1557 if (info.mallocHeapLatin1 > 0) { 1558 REPORT_BYTES(path + "malloc-heap/latin1"_ns, KIND_HEAP, 1559 info.mallocHeapLatin1, 1560 "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); 1561 } 1562 1563 if (info.mallocHeapTwoByte > 0) { 1564 REPORT_BYTES( 1565 path + "malloc-heap/two-byte"_ns, KIND_HEAP, info.mallocHeapTwoByte, 1566 "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); 1567 } 1568 } 1569 1570 nsCString nonNotablePath = pathPrefix; 1571 nonNotablePath += (zStats.isTotals || anonymize) 1572 ? "strings/"_ns 1573 : "strings/string(<non-notable strings>)/"_ns; 1574 1575 if (zStats.stringInfo.gcHeapLatin1 > 0) { 1576 REPORT_GC_BYTES(nonNotablePath + "gc-heap/latin1"_ns, 1577 zStats.stringInfo.gcHeapLatin1, 1578 "Latin1 strings. " MAYBE_INLINE); 1579 } 1580 1581 if (zStats.stringInfo.gcHeapTwoByte > 0) { 1582 REPORT_GC_BYTES(nonNotablePath + "gc-heap/two-byte"_ns, 1583 zStats.stringInfo.gcHeapTwoByte, 1584 "TwoByte strings. " MAYBE_INLINE); 1585 } 1586 1587 if (zStats.stringInfo.mallocHeapLatin1 > 0) { 1588 REPORT_BYTES(nonNotablePath + "malloc-heap/latin1"_ns, KIND_HEAP, 1589 zStats.stringInfo.mallocHeapLatin1, 1590 "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); 1591 } 1592 1593 if (zStats.stringInfo.mallocHeapTwoByte > 0) { 1594 REPORT_BYTES(nonNotablePath + "malloc-heap/two-byte"_ns, KIND_HEAP, 1595 zStats.stringInfo.mallocHeapTwoByte, 1596 "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); 1597 } 1598 1599 if (stringsNotableAboutMemoryGCHeap > 0) { 1600 MOZ_ASSERT(!zStats.isTotals); 1601 REPORT_GC_BYTES( 1602 pathPrefix + "strings/string(<about-memory>)/gc-heap"_ns, 1603 stringsNotableAboutMemoryGCHeap, 1604 "Strings that contain the characters '" STRING_LENGTH 1605 "', which " 1606 "are probably from about:memory itself." MAYBE_INLINE 1607 " We filter them out rather than display them, because displaying " 1608 "them would create even more such strings every time about:memory " 1609 "is refreshed."); 1610 } 1611 1612 if (stringsNotableAboutMemoryMallocHeap > 0) { 1613 MOZ_ASSERT(!zStats.isTotals); 1614 REPORT_BYTES( 1615 pathPrefix + "strings/string(<about-memory>)/malloc-heap"_ns, KIND_HEAP, 1616 stringsNotableAboutMemoryMallocHeap, 1617 "Non-inline string characters of strings that contain the " 1618 "characters '" STRING_LENGTH 1619 "', which are probably from " 1620 "about:memory itself. " MAYBE_OVERALLOCATED 1621 " We filter them out rather than display them, because displaying " 1622 "them would create even more such strings every time about:memory " 1623 "is refreshed."); 1624 } 1625 1626 const JS::ShapeInfo& shapeInfo = zStats.shapeInfo; 1627 if (shapeInfo.shapesGCHeapShared > 0) { 1628 REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/shared"_ns, 1629 shapeInfo.shapesGCHeapShared, "Shared shapes."); 1630 } 1631 1632 if (shapeInfo.shapesGCHeapDict > 0) { 1633 REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/dict"_ns, 1634 shapeInfo.shapesGCHeapDict, "Shapes in dictionary mode."); 1635 } 1636 1637 if (shapeInfo.shapesGCHeapBase > 0) { 1638 REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/base"_ns, 1639 shapeInfo.shapesGCHeapBase, 1640 "Base shapes, which collate data common to many shapes."); 1641 } 1642 1643 if (shapeInfo.shapesMallocHeapCache > 0) { 1644 REPORT_BYTES(pathPrefix + "shapes/malloc-heap/shape-cache"_ns, KIND_HEAP, 1645 shapeInfo.shapesMallocHeapCache, 1646 "Shape cache hash set for adding properties."); 1647 } 1648 1649 if (sundriesGCHeap > 0) { 1650 // We deliberately don't use ZRREPORT_GC_BYTES here. 1651 REPORT_GC_BYTES( 1652 pathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, 1653 "The sum of all 'gc-heap' measurements that are too small to be " 1654 "worth showing individually."); 1655 } 1656 1657 if (sundriesMallocHeap > 0) { 1658 // We deliberately don't use ZRREPORT_BYTES here. 1659 REPORT_BYTES( 1660 pathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, sundriesMallocHeap, 1661 "The sum of all 'malloc-heap' measurements that are too small to " 1662 "be worth showing individually."); 1663 } 1664 1665 if (sundriesNonHeap > 0) { 1666 // We deliberately don't use ZRREPORT_NONHEAP_BYTES here. 1667 REPORT_BYTES(pathPrefix + "sundries/other-heap"_ns, KIND_NONHEAP, 1668 sundriesNonHeap, 1669 "The sum of non-malloc/gc measurements that are too small to " 1670 "be worth showing individually."); 1671 } 1672 1673 if (gcTotalOut) { 1674 *gcTotalOut += gcTotal; 1675 } 1676 1677 #undef STRING_LENGTH 1678 } 1679 1680 static void ReportClassStats(const ClassInfo& classInfo, const nsACString& path, 1681 nsIHandleReportCallback* handleReport, 1682 nsISupports* data, size_t& gcTotal) { 1683 // We deliberately don't use ZRREPORT_BYTES, so that these per-class values 1684 // don't go into sundries. 1685 1686 if (classInfo.objectsGCHeap > 0) { 1687 REPORT_GC_BYTES(path + "objects/gc-heap"_ns, classInfo.objectsGCHeap, 1688 "Objects, including fixed slots."); 1689 } 1690 1691 if (classInfo.objectsMallocHeapSlots > 0) { 1692 REPORT_BYTES(path + "objects/gc-buffers/slots"_ns, KIND_NONHEAP, 1693 classInfo.objectsMallocHeapSlots, "Non-fixed object slots."); 1694 } 1695 1696 if (classInfo.objectsMallocHeapElementsNormal > 0) { 1697 REPORT_BYTES(path + "objects/gc-buffers/elements/normal"_ns, KIND_NONHEAP, 1698 classInfo.objectsMallocHeapElementsNormal, 1699 "Normal (non-wasm) indexed elements."); 1700 } 1701 1702 if (classInfo.objectsMallocHeapElementsAsmJS > 0) { 1703 REPORT_BYTES(path + "objects/malloc-heap/elements/asm.js"_ns, KIND_HEAP, 1704 classInfo.objectsMallocHeapElementsAsmJS, 1705 "asm.js array buffer elements allocated in the malloc heap."); 1706 } 1707 1708 if (classInfo.objectsMallocHeapGlobalData > 0) { 1709 REPORT_BYTES(path + "objects/malloc-heap/global-data"_ns, KIND_HEAP, 1710 classInfo.objectsMallocHeapGlobalData, 1711 "Data for global objects."); 1712 } 1713 1714 if (classInfo.objectsMallocHeapMisc > 0) { 1715 REPORT_BYTES(path + "objects/malloc-heap/misc"_ns, KIND_HEAP, 1716 classInfo.objectsMallocHeapMisc, "Miscellaneous object data."); 1717 } 1718 1719 if (classInfo.objectsNonHeapElementsNormal > 0) { 1720 REPORT_BYTES(path + "objects/non-heap/elements/normal"_ns, KIND_NONHEAP, 1721 classInfo.objectsNonHeapElementsNormal, 1722 "Memory-mapped non-shared array buffer elements."); 1723 } 1724 1725 if (classInfo.objectsNonHeapElementsShared > 0) { 1726 REPORT_BYTES( 1727 path + "objects/non-heap/elements/shared"_ns, KIND_NONHEAP, 1728 classInfo.objectsNonHeapElementsShared, 1729 "Memory-mapped shared array buffer elements. These elements are " 1730 "shared between one or more runtimes; the reported size is divided " 1731 "by the buffer's refcount."); 1732 } 1733 1734 // WebAssembly memories are always non-heap-allocated (mmap). We never put 1735 // these under sundries, because (a) in practice they're almost always 1736 // larger than the sundries threshold, and (b) we'd need a third category of 1737 // sundries ("non-heap"), which would be a pain. 1738 if (classInfo.objectsNonHeapElementsWasm > 0) { 1739 REPORT_BYTES(path + "objects/non-heap/elements/wasm"_ns, KIND_NONHEAP, 1740 classInfo.objectsNonHeapElementsWasm, 1741 "wasm/asm.js array buffer elements allocated outside both the " 1742 "malloc heap and the GC heap."); 1743 } 1744 if (classInfo.objectsNonHeapElementsWasmShared > 0) { 1745 REPORT_BYTES( 1746 path + "objects/non-heap/elements/wasm-shared"_ns, KIND_NONHEAP, 1747 classInfo.objectsNonHeapElementsWasmShared, 1748 "wasm/asm.js array buffer elements allocated outside both the " 1749 "malloc heap and the GC heap. These elements are shared between " 1750 "one or more runtimes; the reported size is divided by the " 1751 "buffer's refcount."); 1752 } 1753 1754 if (classInfo.objectsNonHeapCodeWasm > 0) { 1755 REPORT_BYTES(path + "objects/non-heap/code/wasm"_ns, KIND_NONHEAP, 1756 classInfo.objectsNonHeapCodeWasm, 1757 "AOT-compiled wasm/asm.js code."); 1758 } 1759 } 1760 1761 static void ReportRealmStats(const JS::RealmStats& realmStats, 1762 const xpc::RealmStatsExtras& extras, 1763 nsIHandleReportCallback* handleReport, 1764 nsISupports* data, size_t* gcTotalOut = nullptr) { 1765 static const nsDependentCString addonPrefix("explicit/add-ons/"); 1766 1767 size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; 1768 nsAutoCString realmJSPathPrefix(extras.jsPathPrefix); 1769 nsAutoCString realmDOMPathPrefix(extras.domPathPrefix); 1770 1771 MOZ_ASSERT(!gcTotalOut == realmStats.isTotals); 1772 1773 nsCString nonNotablePath = realmJSPathPrefix; 1774 nonNotablePath += realmStats.isTotals 1775 ? "classes/"_ns 1776 : "classes/class(<non-notable classes>)/"_ns; 1777 1778 ReportClassStats(realmStats.classInfo, nonNotablePath, handleReport, data, 1779 gcTotal); 1780 1781 for (size_t i = 0; i < realmStats.notableClasses.length(); i++) { 1782 MOZ_ASSERT(!realmStats.isTotals); 1783 const JS::NotableClassInfo& classInfo = realmStats.notableClasses[i]; 1784 1785 nsCString classPath = 1786 realmJSPathPrefix + 1787 nsPrintfCString("classes/class(%s)/", classInfo.className_.get()); 1788 1789 ReportClassStats(classInfo, classPath, handleReport, data, gcTotal); 1790 } 1791 1792 // Note that we use realmDOMPathPrefix here. This is because we measure 1793 // orphan DOM nodes in the JS reporter, but we want to report them in a "dom" 1794 // sub-tree rather than a "js" sub-tree. 1795 ZRREPORT_BYTES( 1796 realmDOMPathPrefix + "orphan-nodes"_ns, realmStats.objectsPrivate, 1797 "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " 1798 "objects."); 1799 1800 ZRREPORT_GC_BYTES( 1801 realmJSPathPrefix + "scripts/gc-heap"_ns, realmStats.scriptsGCHeap, 1802 "JSScript instances. There is one per user-defined function in a " 1803 "script, and one for the top-level code in a script."); 1804 1805 ZRREPORT_BYTES(realmJSPathPrefix + "scripts/malloc-heap/data"_ns, 1806 realmStats.scriptsMallocHeapData, 1807 "Various variable-length tables in JSScripts."); 1808 1809 ZRREPORT_BYTES(realmJSPathPrefix + "baseline/data"_ns, 1810 realmStats.baselineData, 1811 "The Baseline JIT's compilation data (BaselineScripts)."); 1812 1813 ZRREPORT_BYTES(realmJSPathPrefix + "alloc-sites"_ns, realmStats.allocSites, 1814 "GC allocation site data associated with IC stubs."); 1815 1816 ZRREPORT_BYTES(realmJSPathPrefix + "ion-data"_ns, realmStats.ionData, 1817 "The IonMonkey JIT's compilation data (IonScripts)."); 1818 1819 ZRREPORT_BYTES(realmJSPathPrefix + "jit-scripts"_ns, realmStats.jitScripts, 1820 "JIT data associated with scripts."); 1821 1822 ZRREPORT_BYTES(realmJSPathPrefix + "realm-object"_ns, realmStats.realmObject, 1823 "The JS::Realm object itself."); 1824 1825 ZRREPORT_BYTES(realmJSPathPrefix + "realm-tables"_ns, realmStats.realmTables, 1826 "Realm-wide tables storing wasm instances."); 1827 1828 ZRREPORT_BYTES(realmJSPathPrefix + "inner-views"_ns, 1829 realmStats.innerViewsTable, 1830 "The table for array buffer inner views."); 1831 1832 ZRREPORT_BYTES( 1833 realmJSPathPrefix + "object-metadata"_ns, realmStats.objectMetadataTable, 1834 "The table used by debugging tools for tracking object metadata"); 1835 1836 ZRREPORT_BYTES(realmJSPathPrefix + "saved-stacks-set"_ns, 1837 realmStats.savedStacksSet, "The saved stacks set."); 1838 1839 ZRREPORT_BYTES(realmJSPathPrefix + "non-syntactic-lexical-scopes-table"_ns, 1840 realmStats.nonSyntacticLexicalScopesTable, 1841 "The non-syntactic lexical scopes table."); 1842 1843 if (sundriesGCHeap > 0) { 1844 // We deliberately don't use ZRREPORT_GC_BYTES here. 1845 REPORT_GC_BYTES( 1846 realmJSPathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, 1847 "The sum of all 'gc-heap' measurements that are too small to be " 1848 "worth showing individually."); 1849 } 1850 1851 if (sundriesMallocHeap > 0) { 1852 // We deliberately don't use ZRREPORT_BYTES here. 1853 REPORT_BYTES( 1854 realmJSPathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, 1855 sundriesMallocHeap, 1856 "The sum of all 'malloc-heap' measurements that are too small to " 1857 "be worth showing individually."); 1858 } 1859 1860 if (gcTotalOut) { 1861 *gcTotalOut += gcTotal; 1862 } 1863 } 1864 1865 static void ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo, 1866 const nsACString& path, 1867 nsIHandleReportCallback* handleReport, 1868 nsISupports* data, size_t& rtTotal) { 1869 if (scriptSourceInfo.misc > 0) { 1870 RREPORT_BYTES(path + "misc"_ns, KIND_HEAP, scriptSourceInfo.misc, 1871 "Miscellaneous data relating to JavaScript source code."); 1872 } 1873 } 1874 1875 void ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, 1876 const nsACString& rtPath, 1877 nsIHandleReportCallback* handleReport, 1878 nsISupports* data, bool anonymize, 1879 size_t* rtTotalOut) { 1880 size_t gcTotal = 0; 1881 1882 for (const auto& zStats : rtStats.zoneStatsVector) { 1883 const xpc::ZoneStatsExtras* extras = 1884 static_cast<const xpc::ZoneStatsExtras*>(zStats.extra); 1885 ReportZoneStats(zStats, *extras, handleReport, data, anonymize, &gcTotal); 1886 } 1887 1888 for (const auto& realmStats : rtStats.realmStatsVector) { 1889 const xpc::RealmStatsExtras* extras = 1890 static_cast<const xpc::RealmStatsExtras*>(realmStats.extra); 1891 1892 ReportRealmStats(realmStats, *extras, handleReport, data, &gcTotal); 1893 } 1894 1895 // Report the rtStats.runtime numbers under "runtime/", and compute their 1896 // total for later. 1897 1898 size_t rtTotal = 0; 1899 1900 RREPORT_BYTES(rtPath + "runtime/runtime-object"_ns, KIND_HEAP, 1901 rtStats.runtime.object, "The JSRuntime object."); 1902 1903 RREPORT_BYTES(rtPath + "runtime/atoms-table"_ns, KIND_HEAP, 1904 rtStats.runtime.atomsTable, "The atoms table."); 1905 1906 RREPORT_BYTES(rtPath + "runtime/atoms-mark-bitmaps"_ns, KIND_HEAP, 1907 rtStats.runtime.atomsMarkBitmaps, 1908 "Mark bitmaps for atoms held by each zone."); 1909 1910 RREPORT_BYTES(rtPath + "runtime/self-host-stencil"_ns, KIND_HEAP, 1911 rtStats.runtime.selfHostStencil, 1912 "The self-hosting CompilationStencil."); 1913 1914 RREPORT_BYTES(rtPath + "runtime/contexts"_ns, KIND_HEAP, 1915 rtStats.runtime.contexts, 1916 "JSContext objects and structures that belong to them."); 1917 1918 RREPORT_BYTES( 1919 rtPath + "runtime/temporary"_ns, KIND_HEAP, rtStats.runtime.temporary, 1920 "Transient data (mostly parse nodes) held by the JSRuntime during " 1921 "compilation."); 1922 1923 RREPORT_BYTES(rtPath + "runtime/interpreter-stack"_ns, KIND_HEAP, 1924 rtStats.runtime.interpreterStack, "JS interpreter frames."); 1925 1926 RREPORT_BYTES( 1927 rtPath + "runtime/shared-immutable-strings-cache"_ns, KIND_HEAP, 1928 rtStats.runtime.sharedImmutableStringsCache, 1929 "Immutable strings (such as JS scripts' source text) shared across all " 1930 "JSRuntimes."); 1931 1932 RREPORT_BYTES(rtPath + "runtime/shared-intl-data"_ns, KIND_HEAP, 1933 rtStats.runtime.sharedIntlData, 1934 "Shared internationalization data."); 1935 1936 RREPORT_BYTES(rtPath + "runtime/uncompressed-source-cache"_ns, KIND_HEAP, 1937 rtStats.runtime.uncompressedSourceCache, 1938 "The uncompressed source code cache."); 1939 1940 RREPORT_BYTES(rtPath + "runtime/script-data"_ns, KIND_HEAP, 1941 rtStats.runtime.scriptData, 1942 "The table holding script data shared in the runtime."); 1943 1944 nsCString nonNotablePath = 1945 rtPath + 1946 nsPrintfCString( 1947 "runtime/script-sources/source(scripts=%d, <non-notable files>)/", 1948 rtStats.runtime.scriptSourceInfo.numScripts); 1949 1950 ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, nonNotablePath, 1951 handleReport, data, rtTotal); 1952 1953 for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) { 1954 const JS::NotableScriptSourceInfo& scriptSourceInfo = 1955 rtStats.runtime.notableScriptSources[i]; 1956 1957 // Escape / to \ before we put the filename into the memory reporter 1958 // path, because we don't want any forward slashes in the string to 1959 // count as path separators. Consumers of memory reporters (e.g. 1960 // about:memory) will convert them back to / after doing path 1961 // splitting. 1962 nsCString escapedFilename; 1963 if (anonymize) { 1964 escapedFilename.AppendPrintf("<anonymized-source-%d>", int(i)); 1965 } else { 1966 nsDependentCString filename(scriptSourceInfo.filename_.get()); 1967 escapedFilename.Append(filename); 1968 escapedFilename.ReplaceSubstring("/", "\\"); 1969 } 1970 1971 nsCString notablePath = 1972 rtPath + 1973 nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/", 1974 scriptSourceInfo.numScripts, escapedFilename.get()); 1975 1976 ReportScriptSourceStats(scriptSourceInfo, notablePath, handleReport, data, 1977 rtTotal); 1978 } 1979 1980 RREPORT_BYTES(rtPath + "runtime/gc/marker"_ns, KIND_HEAP, 1981 rtStats.runtime.gc.marker, "The GC mark stack and gray roots."); 1982 1983 RREPORT_BYTES(rtPath + "runtime/gc/nursery-committed"_ns, KIND_NONHEAP, 1984 rtStats.runtime.gc.nurseryCommitted, 1985 "Memory being used by the GC's nursery."); 1986 1987 RREPORT_BYTES( 1988 rtPath + "runtime/gc/nursery-malloced-buffers"_ns, KIND_HEAP, 1989 rtStats.runtime.gc.nurseryMallocedBuffers, 1990 "Out-of-line slots and elements belonging to objects in the nursery."); 1991 1992 RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/vals"_ns, KIND_HEAP, 1993 rtStats.runtime.gc.storeBufferVals, 1994 "Values in the store buffer."); 1995 1996 RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/cells"_ns, KIND_HEAP, 1997 rtStats.runtime.gc.storeBufferCells, 1998 "Cells in the store buffer."); 1999 2000 RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/slots"_ns, KIND_HEAP, 2001 rtStats.runtime.gc.storeBufferSlots, 2002 "Slots in the store buffer."); 2003 2004 RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/whole-cells"_ns, KIND_HEAP, 2005 rtStats.runtime.gc.storeBufferWholeCells, 2006 "Whole cells in the store buffer."); 2007 2008 RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/generics"_ns, KIND_HEAP, 2009 rtStats.runtime.gc.storeBufferGenerics, 2010 "Generic things in the store buffer."); 2011 2012 RREPORT_BYTES(rtPath + "runtime/jit-lazylink"_ns, KIND_HEAP, 2013 rtStats.runtime.jitLazyLink, 2014 "IonMonkey compilations waiting for lazy linking."); 2015 2016 if (rtTotalOut) { 2017 *rtTotalOut = rtTotal; 2018 } 2019 2020 // Report GC numbers that don't belong to a realm. 2021 2022 // We don't want to report decommitted memory in "explicit", so we just 2023 // change the leading "explicit/" to "decommitted/". 2024 nsCString rtPath2(rtPath); 2025 rtPath2.ReplaceLiteral(0, strlen("explicit"), "decommitted"); 2026 2027 REPORT_GC_BYTES( 2028 rtPath2 + "gc-heap/decommitted-pages"_ns, rtStats.gcHeapDecommittedPages, 2029 "GC arenas in non-empty chunks that is decommitted, i.e. it takes up " 2030 "address space but no physical memory or swap space."); 2031 2032 REPORT_GC_BYTES( 2033 rtPath + "gc-heap/unused-chunks"_ns, rtStats.gcHeapUnusedChunks, 2034 "Empty GC chunks which will soon be released unless claimed for new " 2035 "allocations."); 2036 2037 REPORT_GC_BYTES(rtPath + "gc-heap/unused-arenas"_ns, 2038 rtStats.gcHeapUnusedArenas, 2039 "Empty GC arenas within non-empty chunks."); 2040 2041 REPORT_GC_BYTES(rtPath + "gc-heap/chunk-admin"_ns, rtStats.gcHeapChunkAdmin, 2042 "Bookkeeping information within GC chunks."); 2043 2044 // gcTotal is the sum of everything we've reported for the GC heap. It 2045 // should equal rtStats.gcHeapChunkTotal. 2046 MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal); 2047 } 2048 2049 } // namespace xpc 2050 2051 class JSMainRuntimeRealmsReporter final : public nsIMemoryReporter { 2052 ~JSMainRuntimeRealmsReporter() = default; 2053 2054 public: 2055 NS_DECL_ISUPPORTS 2056 2057 struct Data { 2058 int anonymizeID; 2059 js::Vector<nsCString, 0, js::SystemAllocPolicy> paths; 2060 }; 2061 2062 static void RealmCallback(JSContext* cx, void* vdata, Realm* realm, 2063 const JS::AutoRequireNoGC& nogc) { 2064 // silently ignore OOM errors 2065 Data* data = static_cast<Data*>(vdata); 2066 nsCString path; 2067 GetRealmName(realm, path, &data->anonymizeID, /* replaceSlashes = */ true); 2068 path.Insert(js::IsSystemRealm(realm) ? "js-main-runtime-realms/system/"_ns 2069 : "js-main-runtime-realms/user/"_ns, 2070 0); 2071 (void)data->paths.append(path); 2072 } 2073 2074 NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport, 2075 nsISupports* data, bool anonymize) override { 2076 // First we collect the realm paths. Then we report them. Doing 2077 // the two steps interleaved is a bad idea, because calling 2078 // |handleReport| from within RealmCallback() leads to all manner 2079 // of assertions. 2080 2081 Data d; 2082 d.anonymizeID = anonymize ? 1 : 0; 2083 JS::IterateRealms(XPCJSContext::Get()->Context(), &d, RealmCallback); 2084 2085 for (auto& path : d.paths) { 2086 REPORT(nsCString(path), KIND_OTHER, UNITS_COUNT, 1, 2087 "A live realm in the main JSRuntime."); 2088 } 2089 2090 return NS_OK; 2091 } 2092 }; 2093 2094 NS_IMPL_ISUPPORTS(JSMainRuntimeRealmsReporter, nsIMemoryReporter) 2095 2096 MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf) 2097 2098 namespace xpc { 2099 2100 class OrphanReporter : public JS::ObjectPrivateVisitor { 2101 public: 2102 explicit OrphanReporter(GetISupportsFun aGetISupports) 2103 : JS::ObjectPrivateVisitor(aGetISupports), mState(OrphanMallocSizeOf) {} 2104 2105 virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override { 2106 nsCOMPtr<nsINode> node = do_QueryInterface(aSupports); 2107 if (!node || node->IsInComposedDoc()) { 2108 return 0; 2109 } 2110 2111 // This is an orphan node. If we haven't already handled the sub-tree that 2112 // this node belongs to, measure the sub-tree's size and then record its 2113 // root so we don't measure it again. 2114 nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot(); 2115 if (!orphanTree || mState.HaveSeenPtr(orphanTree.get())) { 2116 return 0; 2117 } 2118 2119 nsWindowSizes sizes(mState); 2120 mozilla::dom::Document::AddSizeOfNodeTree(*orphanTree, sizes); 2121 2122 // We combine the node size with nsStyleSizes here. It's not ideal, but it's 2123 // hard to get the style structs measurements out to nsWindowMemoryReporter. 2124 // Also, we drop mServoData in UnbindFromTree(), so in theory any 2125 // non-in-tree element won't have any style data to measure. 2126 // 2127 // FIXME(emilio): We should ideally not do this, since ShadowRoots keep 2128 // their StyleSheets alive even when detached from a document, and those 2129 // could be significant in theory. 2130 return sizes.getTotalSize(); 2131 } 2132 2133 private: 2134 SizeOfState mState; 2135 }; 2136 2137 #ifdef DEBUG 2138 static bool StartsWithExplicit(nsACString& s) { 2139 return StringBeginsWith(s, "explicit/"_ns); 2140 } 2141 #endif 2142 2143 class XPCJSRuntimeStats : public JS::RuntimeStats { 2144 WindowPaths* mWindowPaths; 2145 WindowPaths* mTopWindowPaths; 2146 int mAnonymizeID; 2147 2148 public: 2149 XPCJSRuntimeStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths, 2150 bool anonymize) 2151 : JS::RuntimeStats(JSMallocSizeOf), 2152 mWindowPaths(windowPaths), 2153 mTopWindowPaths(topWindowPaths), 2154 mAnonymizeID(anonymize ? 1 : 0) {} 2155 2156 ~XPCJSRuntimeStats() { 2157 for (size_t i = 0; i != realmStatsVector.length(); ++i) { 2158 delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra); 2159 } 2160 2161 for (size_t i = 0; i != zoneStatsVector.length(); ++i) { 2162 delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra); 2163 } 2164 } 2165 2166 virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats, 2167 const JS::AutoRequireNoGC& nogc) override { 2168 xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras; 2169 extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/"); 2170 2171 // Get some global in this zone. 2172 Rooted<Realm*> realm(dom::RootingCx(), js::GetAnyRealmInZone(zone)); 2173 if (realm) { 2174 RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); 2175 if (global) { 2176 RefPtr<nsGlobalWindowInner> window; 2177 if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { 2178 // The global is a |window| object. Use the path prefix that 2179 // we should have already created for it. 2180 if (mTopWindowPaths->Get(window->WindowID(), &extras->pathPrefix)) { 2181 extras->pathPrefix.AppendLiteral("/js-"); 2182 } 2183 } 2184 } 2185 } 2186 2187 extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)zone); 2188 2189 MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix)); 2190 2191 zStats->extra = extras; 2192 } 2193 2194 virtual void initExtraRealmStats(Realm* realm, JS::RealmStats* realmStats, 2195 const JS::AutoRequireNoGC& nogc) override { 2196 xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras; 2197 nsCString rName; 2198 GetRealmName(realm, rName, &mAnonymizeID, /* replaceSlashes = */ true); 2199 2200 // Get the realm's global. 2201 bool needZone = true; 2202 RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); 2203 if (global) { 2204 RefPtr<nsGlobalWindowInner> window; 2205 if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { 2206 // The global is a |window| object. Use the path prefix that 2207 // we should have already created for it. 2208 if (mWindowPaths->Get(window->WindowID(), &extras->jsPathPrefix)) { 2209 extras->domPathPrefix.Assign(extras->jsPathPrefix); 2210 extras->domPathPrefix.AppendLiteral("/dom/"); 2211 extras->jsPathPrefix.AppendLiteral("/js-"); 2212 needZone = false; 2213 } else { 2214 extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); 2215 extras->domPathPrefix.AssignLiteral( 2216 "explicit/dom/unknown-window-global?!/"); 2217 } 2218 } else { 2219 extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); 2220 extras->domPathPrefix.AssignLiteral( 2221 "explicit/dom/non-window-global?!/"); 2222 } 2223 } else { 2224 extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); 2225 extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/"); 2226 } 2227 2228 if (needZone) { 2229 extras->jsPathPrefix += 2230 nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(realm)); 2231 } 2232 2233 extras->jsPathPrefix += "realm("_ns + rName + ")/"_ns; 2234 2235 // extras->jsPathPrefix is used for almost all the realm-specific 2236 // reports. At this point it has the form 2237 // "<something>realm(<rname>)/". 2238 // 2239 // extras->domPathPrefix is used for DOM orphan nodes, which are 2240 // counted by the JS reporter but reported as part of the DOM 2241 // measurements. At this point it has the form "<something>/dom/" if 2242 // this realm belongs to an nsGlobalWindow, and 2243 // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't 2244 // be used, because non-nsGlobalWindow realms shouldn't have 2245 // orphan DOM nodes). 2246 2247 MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix)); 2248 MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix)); 2249 2250 realmStats->extra = extras; 2251 } 2252 }; 2253 2254 void JSReporter::CollectReports(WindowPaths* windowPaths, 2255 WindowPaths* topWindowPaths, 2256 nsIHandleReportCallback* handleReport, 2257 nsISupports* data, bool anonymize) { 2258 XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance(); 2259 2260 // In the first step we get all the stats and stash them in a local 2261 // data structure. In the second step we pass all the stashed stats to 2262 // the callback. Separating these steps is important because the 2263 // callback may be a JS function, and executing JS while getting these 2264 // stats seems like a bad idea. 2265 2266 XPCJSRuntimeStats rtStats(windowPaths, topWindowPaths, anonymize); 2267 OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); 2268 JSContext* cx = XPCJSContext::Get()->Context(); 2269 if (!JS::CollectRuntimeStats(cx, &rtStats, &orphanReporter, anonymize)) { 2270 return; 2271 } 2272 2273 // Collect JS stats not associated with a Runtime such as helper threads or 2274 // global tracelogger data. We do this here in JSReporter::CollectReports 2275 // as this is used for the main Runtime in process. 2276 JS::GlobalStats gStats(JSMallocSizeOf); 2277 if (!JS::CollectGlobalStats(&gStats)) { 2278 return; 2279 } 2280 2281 size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf); 2282 2283 size_t wrappedJSSize = 2284 xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf); 2285 2286 XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf); 2287 XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(cx, &sizeInfo); 2288 2289 mozJSModuleLoader* loader = mozJSModuleLoader::Get(); 2290 size_t jsModuleLoaderSize = 2291 loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0; 2292 mozJSModuleLoader* devToolsLoader = mozJSModuleLoader::GetDevToolsLoader(); 2293 size_t jsDevToolsModuleLoaderSize = 2294 devToolsLoader ? devToolsLoader->SizeOfIncludingThis(JSMallocSizeOf) : 0; 2295 2296 // This is the second step (see above). First we report stuff in the 2297 // "explicit" tree, then we report other stuff. 2298 2299 size_t rtTotal = 0; 2300 xpc::ReportJSRuntimeExplicitTreeStats(rtStats, "explicit/js-non-window/"_ns, 2301 handleReport, data, anonymize, 2302 &rtTotal); 2303 2304 // Report the sums of the realm numbers. 2305 xpc::RealmStatsExtras realmExtrasTotal; 2306 realmExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/realms/"); 2307 realmExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/"); 2308 ReportRealmStats(rtStats.realmTotals, realmExtrasTotal, handleReport, data); 2309 2310 xpc::ZoneStatsExtras zExtrasTotal; 2311 zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/"); 2312 ReportZoneStats(rtStats.zTotals, zExtrasTotal, handleReport, data, anonymize); 2313 2314 // Report the sum of the runtime/ numbers. 2315 REPORT_BYTES( 2316 "js-main-runtime/runtime"_ns, KIND_OTHER, rtTotal, 2317 "The sum of all measurements under 'explicit/js-non-window/runtime/'."); 2318 2319 // Report the number of HelperThread 2320 2321 REPORT("js-helper-threads/idle"_ns, KIND_OTHER, UNITS_COUNT, 2322 gStats.helperThread.idleThreadCount, 2323 "The current number of idle JS HelperThreads."); 2324 2325 REPORT( 2326 "js-helper-threads/active"_ns, KIND_OTHER, UNITS_COUNT, 2327 gStats.helperThread.activeThreadCount, 2328 "The current number of active JS HelperThreads. Memory held by these is" 2329 " not reported."); 2330 2331 // Report the numbers for memory used by wasm Runtime state. 2332 REPORT_BYTES("wasm-runtime"_ns, KIND_OTHER, rtStats.runtime.wasmRuntime, 2333 "The memory used for wasm runtime bookkeeping."); 2334 2335 // Although wasm guard pages aren't committed in memory they can be very 2336 // large and contribute greatly to vsize and so are worth reporting. 2337 if (rtStats.runtime.wasmGuardPages > 0) { 2338 REPORT_BYTES( 2339 "wasm-guard-pages"_ns, KIND_OTHER, rtStats.runtime.wasmGuardPages, 2340 "Guard pages mapped after the end of wasm memories, reserved for " 2341 "optimization tricks, but not committed and thus never contributing" 2342 " to RSS, only vsize."); 2343 } 2344 2345 // Report the numbers for memory outside of realms. 2346 2347 REPORT_BYTES("js-main-runtime/gc-heap/unused-chunks"_ns, KIND_OTHER, 2348 rtStats.gcHeapUnusedChunks, 2349 "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); 2350 2351 REPORT_BYTES("js-main-runtime/gc-heap/unused-arenas"_ns, KIND_OTHER, 2352 rtStats.gcHeapUnusedArenas, 2353 "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); 2354 2355 REPORT_BYTES("js-main-runtime/gc-heap/chunk-admin"_ns, KIND_OTHER, 2356 rtStats.gcHeapChunkAdmin, 2357 "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); 2358 2359 // Report a breakdown of the committed GC space. 2360 2361 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/chunks"_ns, KIND_OTHER, 2362 rtStats.gcHeapUnusedChunks, 2363 "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); 2364 2365 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/arenas"_ns, KIND_OTHER, 2366 rtStats.gcHeapUnusedArenas, 2367 "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); 2368 2369 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/objects"_ns, 2370 KIND_OTHER, rtStats.zTotals.unusedGCThings.object, 2371 "Unused object cells within non-empty arenas."); 2372 2373 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/strings"_ns, 2374 KIND_OTHER, rtStats.zTotals.unusedGCThings.string, 2375 "Unused string cells within non-empty arenas."); 2376 2377 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/symbols"_ns, 2378 KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol, 2379 "Unused symbol cells within non-empty arenas."); 2380 2381 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/shapes"_ns, 2382 KIND_OTHER, rtStats.zTotals.unusedGCThings.shape, 2383 "Unused shape cells within non-empty arenas."); 2384 2385 REPORT_BYTES( 2386 "js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"_ns, 2387 KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape, 2388 "Unused base shape cells within non-empty arenas."); 2389 2390 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/bigints"_ns, 2391 KIND_OTHER, rtStats.zTotals.unusedGCThings.bigInt, 2392 "Unused BigInt cells within non-empty arenas."); 2393 2394 REPORT_BYTES( 2395 "js-main-runtime-gc-heap-committed/unused/gc-things/getter-setters"_ns, 2396 KIND_OTHER, rtStats.zTotals.unusedGCThings.getterSetter, 2397 "Unused getter-setter cells within non-empty arenas."); 2398 2399 REPORT_BYTES( 2400 "js-main-runtime-gc-heap-committed/unused/gc-things/property-maps"_ns, 2401 KIND_OTHER, rtStats.zTotals.unusedGCThings.propMap, 2402 "Unused property map cells within non-empty arenas."); 2403 2404 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/scopes"_ns, 2405 KIND_OTHER, rtStats.zTotals.unusedGCThings.scope, 2406 "Unused scope cells within non-empty arenas."); 2407 2408 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/scripts"_ns, 2409 KIND_OTHER, rtStats.zTotals.unusedGCThings.script, 2410 "Unused script cells within non-empty arenas."); 2411 2412 REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"_ns, 2413 KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, 2414 "Unused jitcode cells within non-empty arenas."); 2415 2416 REPORT_BYTES( 2417 "js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"_ns, 2418 KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared, 2419 "Unused regexpshared cells within non-empty arenas."); 2420 2421 REPORT_BYTES( 2422 "js-main-runtime-gc-heap-committed/unused/gc-things/small-buffers"_ns, 2423 KIND_OTHER, rtStats.zTotals.unusedGCThings.smallBuffer, 2424 "Unused small buffer cells within non-empty arenas."); 2425 2426 REPORT_BYTES("js-main-runtime-gc-heap-committed/used/chunk-admin"_ns, 2427 KIND_OTHER, rtStats.gcHeapChunkAdmin, 2428 "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); 2429 2430 REPORT_BYTES("js-main-runtime-gc-heap-committed/used/arena-admin"_ns, 2431 KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, 2432 "The same as 'js-main-runtime/zones/gc-heap-arena-admin'."); 2433 2434 size_t gcThingTotal = 0; 2435 2436 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/objects"_ns, 2437 KIND_OTHER, rtStats.realmTotals.classInfo.objectsGCHeap, 2438 "Used object cells."); 2439 2440 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/strings"_ns, 2441 KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(), 2442 "Used string cells."); 2443 2444 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/symbols"_ns, 2445 KIND_OTHER, rtStats.zTotals.symbolsGCHeap, 2446 "Used symbol cells."); 2447 2448 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/shapes"_ns, 2449 KIND_OTHER, 2450 rtStats.zTotals.shapeInfo.shapesGCHeapShared + 2451 rtStats.zTotals.shapeInfo.shapesGCHeapDict, 2452 "Used shape cells."); 2453 2454 MREPORT_BYTES( 2455 "js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"_ns, 2456 KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase, 2457 "Used base shape cells."); 2458 2459 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/bigints"_ns, 2460 KIND_OTHER, rtStats.zTotals.bigIntsGCHeap, 2461 "Used BigInt cells."); 2462 2463 MREPORT_BYTES( 2464 "js-main-runtime-gc-heap-committed/used/gc-things/getter-setters"_ns, 2465 KIND_OTHER, rtStats.zTotals.getterSettersGCHeap, 2466 "Used getter/setter cells."); 2467 2468 MREPORT_BYTES( 2469 "js-main-runtime-gc-heap-committed/used/gc-things/property-maps"_ns, 2470 KIND_OTHER, 2471 rtStats.zTotals.dictPropMapsGCHeap + 2472 rtStats.zTotals.compactPropMapsGCHeap + 2473 rtStats.zTotals.normalPropMapsGCHeap, 2474 "Used property map cells."); 2475 2476 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/scopes"_ns, 2477 KIND_OTHER, rtStats.zTotals.scopesGCHeap, "Used scope cells."); 2478 2479 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/scripts"_ns, 2480 KIND_OTHER, rtStats.realmTotals.scriptsGCHeap, 2481 "Used script cells."); 2482 2483 MREPORT_BYTES("js-main-runtime-gc-heap-committed/used/gc-things/jitcode"_ns, 2484 KIND_OTHER, rtStats.zTotals.jitCodesGCHeap, 2485 "Used jitcode cells."); 2486 2487 MREPORT_BYTES( 2488 "js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"_ns, 2489 KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap, 2490 "Used regexpshared cells."); 2491 2492 MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings); 2493 (void)gcThingTotal; 2494 2495 // Report totals from per-zone GC buffer allocators. 2496 2497 MREPORT_BYTES("js-main-runtime-gc-buffers/used"_ns, KIND_OTHER, 2498 rtStats.zTotals.gcBuffers.usedBytes, 2499 "Bookeeping information and padding within GC buffer memeory."); 2500 2501 MREPORT_BYTES("js-main-runtime-gc-buffers/free"_ns, KIND_OTHER, 2502 rtStats.zTotals.gcBuffers.freeBytes, 2503 "Free space within GC buffer memeory."); 2504 2505 MREPORT_BYTES("js-main-runtime-gc-buffers/admin"_ns, KIND_OTHER, 2506 rtStats.zTotals.gcBuffers.adminBytes, 2507 "Bookeeping information and padding within GC buffer memeory."); 2508 2509 REPORT("js-main-runtime-zone-count"_ns, KIND_OTHER, UNITS_COUNT, 2510 rtStats.zoneStatsVector.length(), "Count of GC zones in the runtime."); 2511 2512 // Report xpconnect. 2513 2514 REPORT_BYTES("explicit/xpconnect/runtime"_ns, KIND_HEAP, xpcJSRuntimeSize, 2515 "The XPConnect runtime."); 2516 2517 REPORT_BYTES("explicit/xpconnect/wrappedjs"_ns, KIND_HEAP, wrappedJSSize, 2518 "Wrappers used to implement XPIDL interfaces with JS."); 2519 2520 REPORT_BYTES("explicit/xpconnect/scopes"_ns, KIND_HEAP, 2521 sizeInfo.mScopeAndMapSize, "XPConnect scopes."); 2522 2523 REPORT_BYTES("explicit/xpconnect/proto-iface-cache"_ns, KIND_HEAP, 2524 sizeInfo.mProtoAndIfaceCacheSize, 2525 "Prototype and interface binding caches."); 2526 2527 REPORT_BYTES("explicit/xpconnect/js-module-loader"_ns, KIND_HEAP, 2528 jsModuleLoaderSize, "XPConnect's JS module loader."); 2529 REPORT_BYTES("explicit/xpconnect/js-devtools-module-loader"_ns, KIND_HEAP, 2530 jsDevToolsModuleLoaderSize, "DevTools's JS module loader."); 2531 2532 // Report HelperThreadState. 2533 2534 REPORT_BYTES("explicit/js-non-window/helper-thread/heap-other"_ns, KIND_HEAP, 2535 gStats.helperThread.stateData, 2536 "Memory used by HelperThreadState."); 2537 2538 REPORT_BYTES( 2539 "explicit/js-non-window/helper-thread/ion-compile-task"_ns, KIND_HEAP, 2540 gStats.helperThread.ionCompileTask, 2541 "The memory used by IonCompileTasks waiting in HelperThreadState."); 2542 2543 REPORT_BYTES( 2544 "explicit/js-non-window/helper-thread/wasm-compile"_ns, KIND_HEAP, 2545 gStats.helperThread.wasmCompile, 2546 "The memory used by Wasm compilations waiting in HelperThreadState."); 2547 2548 REPORT_BYTES("explicit/js-non-window/helper-thread/contexts"_ns, KIND_HEAP, 2549 gStats.helperThread.contexts, 2550 "The memory used by the JSContexts in HelperThreadState."); 2551 } 2552 2553 static nsresult JSSizeOfTab(JSObject* obj, size_t* jsObjectsSize, 2554 size_t* jsStringsSize, size_t* jsPrivateSize, 2555 size_t* jsOtherSize) { 2556 JSContext* cx = XPCJSContext::Get()->Context(); 2557 JS::Zone* zone = JS::GetObjectZone(obj); 2558 if (JS::IsIncrementalGCInProgress(cx)) { 2559 JS::FinishIncrementalGC(cx, JS::GCReason::PREPARE_FOR_TRACING); 2560 } 2561 JS::AutoCheckCannotGC nogc(cx); 2562 2563 TabSizes sizes; 2564 OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); 2565 NS_ENSURE_TRUE(JS::AddSizeOfTab(cx, zone, moz_malloc_size_of, &orphanReporter, 2566 &sizes, nogc), 2567 NS_ERROR_OUT_OF_MEMORY); 2568 2569 *jsObjectsSize = sizes.objects_; 2570 *jsStringsSize = sizes.strings_; 2571 *jsPrivateSize = sizes.private_; 2572 *jsOtherSize = sizes.other_; 2573 return NS_OK; 2574 } 2575 2576 } // namespace xpc 2577 2578 static void AccumulateTelemetryCallback(JSMetric id, uint32_t sample) { 2579 switch (id) { 2580 case JSMetric::GC_MS: 2581 glean::javascript_gc::total_time.AccumulateRawDuration( 2582 TimeDuration::FromMilliseconds(sample)); 2583 break; 2584 case JSMetric::GC_MINOR_US: 2585 glean::javascript_gc::minor_time.AccumulateRawDuration( 2586 TimeDuration::FromMicroseconds(sample)); 2587 break; 2588 case JSMetric::GC_PREPARE_MS: 2589 glean::javascript_gc::prepare_time.AccumulateRawDuration( 2590 TimeDuration::FromMilliseconds(sample)); 2591 break; 2592 case JSMetric::GC_MARK_ROOTS_US: 2593 glean::javascript_gc::mark_roots_time.AccumulateRawDuration( 2594 TimeDuration::FromMicroseconds(sample)); 2595 break; 2596 case JSMetric::GC_MARK_MS: 2597 glean::javascript_gc::mark_time.AccumulateRawDuration( 2598 TimeDuration::FromMilliseconds(sample)); 2599 break; 2600 case JSMetric::GC_SWEEP_MS: 2601 glean::javascript_gc::sweep_time.AccumulateRawDuration( 2602 TimeDuration::FromMilliseconds(sample)); 2603 break; 2604 case JSMetric::GC_COMPACT_MS: 2605 glean::javascript_gc::compact_time.AccumulateRawDuration( 2606 TimeDuration::FromMilliseconds(sample)); 2607 break; 2608 case JSMetric::GC_SLICE_MS: 2609 glean::javascript_gc::slice_time.AccumulateRawDuration( 2610 TimeDuration::FromMilliseconds(sample)); 2611 break; 2612 case JSMetric::ION_COMPILE_TIME: 2613 glean::javascript_ion::compile_time.AccumulateRawDuration( 2614 TimeDuration::FromMicroseconds(sample)); 2615 break; 2616 case JSMetric::GC_BUDGET_MS_2: 2617 glean::javascript_gc::budget.AccumulateRawDuration( 2618 TimeDuration::FromMilliseconds(sample)); 2619 break; 2620 case JSMetric::GC_BUDGET_OVERRUN: 2621 glean::javascript_gc::budget_overrun.AccumulateRawDuration( 2622 TimeDuration::FromMicroseconds(sample)); 2623 break; 2624 case JSMetric::GC_ANIMATION_MS: 2625 glean::javascript_gc::animation.AccumulateRawDuration( 2626 TimeDuration::FromMilliseconds(sample)); 2627 break; 2628 case JSMetric::GC_MAX_PAUSE_MS_2: 2629 glean::javascript_gc::max_pause.AccumulateRawDuration( 2630 TimeDuration::FromMilliseconds(sample)); 2631 break; 2632 case JSMetric::GC_MARK_GRAY_MS_2: 2633 glean::javascript_gc::mark_gray.AccumulateRawDuration( 2634 TimeDuration::FromMilliseconds(sample)); 2635 break; 2636 case JSMetric::GC_MARK_WEAK_MS: 2637 glean::javascript_gc::mark_weak.AccumulateRawDuration( 2638 TimeDuration::FromMilliseconds(sample)); 2639 break; 2640 case JSMetric::GC_TIME_BETWEEN_S: 2641 glean::javascript_gc::time_between.AccumulateRawDuration( 2642 TimeDuration::FromSeconds(sample)); 2643 break; 2644 case JSMetric::GC_TIME_BETWEEN_SLICES_MS: 2645 glean::javascript_gc::time_between_slices.AccumulateRawDuration( 2646 TimeDuration::FromMilliseconds(sample)); 2647 break; 2648 case JSMetric::GC_TIME_BETWEEN_MINOR_MS: 2649 glean::javascript_gc::time_between_minor.AccumulateRawDuration( 2650 TimeDuration::FromMilliseconds(sample)); 2651 break; 2652 case JSMetric::GC_TASK_START_DELAY_US: 2653 glean::javascript_gc::task_start_delay.AccumulateRawDuration( 2654 TimeDuration::FromMicroseconds(sample)); 2655 break; 2656 case JSMetric::GC_MMU_50: 2657 glean::javascript_gc::mmu_50.AccumulateSingleSample(sample); 2658 break; 2659 case JSMetric::GC_NURSERY_PROMOTION_RATE: 2660 glean::javascript_gc::nursery_promotion_rate.AccumulateSingleSample( 2661 sample); 2662 break; 2663 case JSMetric::GC_TENURED_SURVIVAL_RATE: 2664 glean::javascript_gc::tenured_survival_rate.AccumulateSingleSample( 2665 sample); 2666 break; 2667 case JSMetric::GC_PARALLEL_MARK_UTILIZATION: 2668 glean::javascript_gc::parallel_mark_utilization.AccumulateSingleSample( 2669 sample); 2670 break; 2671 case JSMetric::GC_NURSERY_BYTES_2: 2672 glean::javascript_gc::nursery_bytes.Accumulate(sample); 2673 break; 2674 case JSMetric::GC_EFFECTIVENESS: 2675 glean::javascript_gc::effectiveness.AccumulateSingleSample(sample); 2676 break; 2677 case JSMetric::GC_ZONE_COUNT: 2678 glean::javascript_gc::zone_count.AccumulateSingleSample(sample); 2679 break; 2680 case JSMetric::GC_ZONES_COLLECTED: 2681 glean::javascript_gc::zones_collected.AccumulateSingleSample(sample); 2682 break; 2683 case JSMetric::GC_PRETENURE_COUNT_2: 2684 glean::javascript_gc::pretenure_count.AccumulateSingleSample(sample); 2685 break; 2686 case JSMetric::GC_MARK_RATE_2: 2687 glean::javascript_gc::mark_rate.AccumulateSingleSample(sample); 2688 break; 2689 case JSMetric::GC_SLICE_COUNT: 2690 glean::javascript_gc::slice_count.AccumulateSingleSample(sample); 2691 break; 2692 case JSMetric::GC_PARALLEL_MARK_SPEEDUP: 2693 glean::javascript_gc::parallel_mark_speedup.AccumulateSingleSample( 2694 sample); 2695 break; 2696 case JSMetric::GC_PARALLEL_MARK_INTERRUPTIONS: 2697 glean::javascript_gc::parallel_mark_interruptions.AccumulateSingleSample( 2698 sample); 2699 break; 2700 case JSMetric::GC_IS_COMPARTMENTAL: 2701 if (sample) { 2702 glean::javascript_gc::is_zone_gc 2703 .EnumGet(glean::javascript_gc::IsZoneGcLabel::eTrue) 2704 .Add(1); 2705 } else { 2706 glean::javascript_gc::is_zone_gc 2707 .EnumGet(glean::javascript_gc::IsZoneGcLabel::eFalse) 2708 .Add(1); 2709 } 2710 break; 2711 case JSMetric::GC_BUDGET_WAS_INCREASED: 2712 if (sample) { 2713 glean::javascript_gc::budget_was_increased 2714 .EnumGet(glean::javascript_gc::BudgetWasIncreasedLabel::eTrue) 2715 .Add(1); 2716 } else { 2717 glean::javascript_gc::budget_was_increased 2718 .EnumGet(glean::javascript_gc::BudgetWasIncreasedLabel::eFalse) 2719 .Add(1); 2720 } 2721 break; 2722 case JSMetric::GC_SLICE_WAS_LONG: 2723 if (sample) { 2724 glean::javascript_gc::slice_was_long 2725 .EnumGet(glean::javascript_gc::SliceWasLongLabel::eTrue) 2726 .Add(1); 2727 } else { 2728 glean::javascript_gc::slice_was_long 2729 .EnumGet(glean::javascript_gc::SliceWasLongLabel::eFalse) 2730 .Add(1); 2731 } 2732 break; 2733 case JSMetric::GC_RESET: 2734 if (sample) { 2735 glean::javascript_gc::reset 2736 .EnumGet(glean::javascript_gc::ResetLabel::eTrue) 2737 .Add(1); 2738 } else { 2739 glean::javascript_gc::reset 2740 .EnumGet(glean::javascript_gc::ResetLabel::eFalse) 2741 .Add(1); 2742 } 2743 break; 2744 case JSMetric::GC_NON_INCREMENTAL: 2745 if (sample) { 2746 glean::javascript_gc::non_incremental 2747 .EnumGet(glean::javascript_gc::NonIncrementalLabel::eTrue) 2748 .Add(1); 2749 } else { 2750 glean::javascript_gc::non_incremental 2751 .EnumGet(glean::javascript_gc::NonIncrementalLabel::eFalse) 2752 .Add(1); 2753 } 2754 break; 2755 case JSMetric::GC_PARALLEL_MARK: 2756 if (sample) { 2757 glean::javascript_gc::parallel_mark_used 2758 .EnumGet(glean::javascript_gc::ParallelMarkUsedLabel::eTrue) 2759 .Add(1); 2760 } else { 2761 glean::javascript_gc::parallel_mark_used 2762 .EnumGet(glean::javascript_gc::ParallelMarkUsedLabel::eFalse) 2763 .Add(1); 2764 } 2765 break; 2766 case JSMetric::GC_REASON_2: { 2767 // Assert that every reason has an associated glean label. 2768 static_assert( 2769 static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) + 1 == 2770 static_cast<uint8_t>( 2771 glean::javascript_gc::ReasonLabel::e__Other__), 2772 "GC reason enum and glean::javascript_gc::reason labels do " 2773 "not match."); 2774 MOZ_ASSERT(static_cast<JS::GCReason>(sample) <= 2775 JS::GCReason::LAST_FIREFOX_REASON, 2776 "Invalid GC Reason."); 2777 2778 nsAutoCString reason( 2779 JS::ExplainGCReason(static_cast<JS::GCReason>(sample))); 2780 glean::javascript_gc::reason.Get(reason).Add(1); 2781 } break; 2782 case JSMetric::GC_RESET_REASON: { 2783 MOZ_ASSERT( 2784 sample < static_cast<uint32_t>( 2785 glean::javascript_gc::ResetReasonLabel::e__Other__), 2786 "Reason does not exist in the reset_reason labels list."); 2787 nsAutoCString reason(JS::ExplainGCAbortReason(sample)); 2788 glean::javascript_gc::reset_reason.Get(reason).Add(1); 2789 } break; 2790 case JSMetric::GC_NON_INCREMENTAL_REASON: { 2791 MOZ_ASSERT( 2792 sample < 2793 static_cast<uint32_t>( 2794 glean::javascript_gc::NonIncrementalReasonLabel::e__Other__), 2795 "Reason does not exist in the non_incremental_reason labels list."); 2796 nsAutoCString reason(JS::ExplainGCAbortReason(sample)); 2797 glean::javascript_gc::non_incremental_reason.Get(reason).Add(1); 2798 } break; 2799 case JSMetric::GC_MINOR_REASON: { 2800 // Assert that every reason has an associated glean label. 2801 static_assert( 2802 static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) + 1 == 2803 static_cast<uint8_t>( 2804 glean::javascript_gc::MinorReasonLabel::e__Other__), 2805 "GC reason enum and glean::javascript_gc::reason labels do not " 2806 "match."); 2807 MOZ_ASSERT(static_cast<JS::GCReason>(sample) <= 2808 JS::GCReason::LAST_FIREFOX_REASON, 2809 "Invalid GC Reason."); 2810 2811 nsAutoCString reason( 2812 JS::ExplainGCReason(static_cast<JS::GCReason>(sample))); 2813 glean::javascript_gc::minor_reason.Get(reason).Add(1); 2814 } break; 2815 case JSMetric::GC_MINOR_REASON_LONG: { 2816 // Assert that every reason has an associated glean label. 2817 static_assert( 2818 static_cast<uint8_t>(JS::GCReason::LAST_FIREFOX_REASON) + 1 == 2819 static_cast<uint8_t>( 2820 glean::javascript_gc::MinorReasonLongLabel::e__Other__), 2821 "GC reason enum and glean::javascript_gc::reason labels do not " 2822 "match."); 2823 MOZ_ASSERT(static_cast<JS::GCReason>(sample) <= 2824 JS::GCReason::LAST_FIREFOX_REASON, 2825 "Invalid GC Reason."); 2826 2827 nsAutoCString reason( 2828 JS::ExplainGCReason(static_cast<JS::GCReason>(sample))); 2829 glean::javascript_gc::minor_reason_long.Get(reason).Add(1); 2830 } break; 2831 case JSMetric::GC_SLOW_PHASE: { 2832 MOZ_ASSERT(sample < static_cast<uint32_t>( 2833 glean::javascript_gc::SlowPhaseLabel::e__Other__), 2834 "Phase does not exist in the slow_phase labels list."); 2835 nsAutoCString phase(JS::GetGCPhaseName(sample)); 2836 glean::javascript_gc::slow_phase.Get(phase).Add(1); 2837 } break; 2838 case JSMetric::GC_SLOW_TASK: { 2839 MOZ_ASSERT(sample < static_cast<uint32_t>( 2840 glean::javascript_gc::SlowTaskLabel::e__Other__), 2841 "Phase does not exist in the slow_task labels list."); 2842 nsAutoCString phase(JS::GetGCPhaseName(sample)); 2843 glean::javascript_gc::slow_task.Get(phase).Add(1); 2844 } break; 2845 2846 default: 2847 // The rest aren't relayed to Glean. 2848 break; 2849 } 2850 } 2851 2852 static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) { 2853 switch (counter) { 2854 case JSUseCounter::ASMJS: 2855 SetUseCounter(obj, eUseCounter_custom_JS_asmjs); 2856 return; 2857 case JSUseCounter::WASM: 2858 SetUseCounter(obj, eUseCounter_custom_JS_wasm); 2859 return; 2860 case JSUseCounter::USE_ASM: 2861 SetUseCounter(obj, eUseCounter_custom_JS_use_asm); 2862 return; 2863 case JSUseCounter::WASM_LEGACY_EXCEPTIONS: 2864 SetUseCounter(obj, eUseCounter_custom_JS_wasm_legacy_exceptions); 2865 return; 2866 case JSUseCounter::ISHTMLDDA_FUSE: 2867 SetUseCounter(obj, eUseCounter_custom_JS_isHTMLDDA_fuse); 2868 return; 2869 case JSUseCounter::OPTIMIZE_GET_ITERATOR_FUSE: 2870 SetUseCounter(obj, eUseCounter_custom_JS_optimizeGetIterator_fuse); 2871 return; 2872 case JSUseCounter::OPTIMIZE_ARRAY_SPECIES_FUSE: 2873 SetUseCounter(obj, eUseCounter_custom_JS_optimizeArraySpecies_fuse); 2874 return; 2875 case JSUseCounter::OPTIMIZE_PROMISE_LOOKUP_FUSE: 2876 SetUseCounter(obj, eUseCounter_custom_JS_optimizePromiseLookup_fuse); 2877 return; 2878 case JSUseCounter::THENABLE_USE: 2879 SetUseCounter(obj, eUseCounter_custom_JS_thenable); 2880 return; 2881 case JSUseCounter::THENABLE_USE_PROTO: 2882 SetUseCounter(obj, eUseCounter_custom_JS_thenable_proto); 2883 return; 2884 case JSUseCounter::THENABLE_USE_STANDARD_PROTO: 2885 SetUseCounter(obj, eUseCounter_custom_JS_thenable_standard_proto); 2886 return; 2887 case JSUseCounter::THENABLE_USE_OBJECT_PROTO: 2888 SetUseCounter(obj, eUseCounter_custom_JS_thenable_object_proto); 2889 return; 2890 case JSUseCounter::LEGACY_LANG_SUBTAG: 2891 SetUseCounter(obj, eUseCounter_custom_JS_legacy_lang_subtag); 2892 return; 2893 case JSUseCounter::IC_STUB_TOO_LARGE: 2894 SetUseCounter(obj, eUseCounter_custom_JS_ic_stub_too_large); 2895 return; 2896 case JSUseCounter::IC_STUB_OOM: 2897 SetUseCounter(obj, eUseCounter_custom_JS_ic_stub_oom); 2898 return; 2899 case JSUseCounter::DATEPARSE: 2900 SetUseCounter(obj, eUseCounter_custom_JS_dateparse); 2901 return; 2902 case JSUseCounter::DATEPARSE_IMPL_DEF: 2903 SetUseCounter(obj, eUseCounter_custom_JS_dateparse_impl_def); 2904 return; 2905 case JSUseCounter::COUNT: 2906 break; 2907 } 2908 MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id"); 2909 } 2910 2911 static void GetRealmNameCallback(JSContext* cx, Realm* realm, char* buf, 2912 size_t bufsize, 2913 const JS::AutoRequireNoGC& nogc) { 2914 nsCString name; 2915 // This is called via the JSAPI and isn't involved in memory reporting, so 2916 // we don't need to anonymize realm names. 2917 int anonymizeID = 0; 2918 GetRealmName(realm, name, &anonymizeID, /* replaceSlashes = */ false); 2919 if (name.Length() >= bufsize) { 2920 name.Truncate(bufsize - 1); 2921 } 2922 memcpy(buf, name.get(), name.Length() + 1); 2923 } 2924 2925 static void DestroyRealm(JS::GCContext* gcx, JS::Realm* realm) { 2926 // Get the current compartment private into an AutoPtr (which will do the 2927 // cleanup for us), and null out the private field. 2928 mozilla::UniquePtr<RealmPrivate> priv(RealmPrivate::Get(realm)); 2929 JS::SetRealmPrivate(realm, nullptr); 2930 } 2931 2932 static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) { 2933 MOZ_ASSERT(cx); 2934 MOZ_ASSERT(obj); 2935 MOZ_ASSERT(mozilla::dom::IsDOMObject(obj)); 2936 2937 if (!mozilla::dom::TryPreserveWrapper(obj)) { 2938 return false; 2939 } 2940 2941 MOZ_ASSERT(!mozilla::dom::HasReleasedWrapper(obj), 2942 "There should be no released wrapper since we just preserved it"); 2943 2944 return true; 2945 } 2946 2947 static nsresult ReadSourceFromFilename(JSContext* cx, const char* filename, 2948 char16_t** twoByteSource, 2949 char** utf8Source, size_t* len) { 2950 MOZ_ASSERT(*len == 0); 2951 MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), 2952 "must be called requesting only one of UTF-8 or UTF-16 source"); 2953 MOZ_ASSERT_IF(twoByteSource, !*twoByteSource); 2954 MOZ_ASSERT_IF(utf8Source, !*utf8Source); 2955 2956 nsresult rv; 2957 2958 // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with 2959 // the filename of its caller. Axe that if present. 2960 const char* arrow; 2961 while ((arrow = strstr(filename, " -> "))) { 2962 filename = arrow + strlen(" -> "); 2963 } 2964 2965 // Get the URI. 2966 nsCOMPtr<nsIURI> uri; 2967 rv = NS_NewURI(getter_AddRefs(uri), filename); 2968 NS_ENSURE_SUCCESS(rv, rv); 2969 2970 nsCOMPtr<nsIChannel> scriptChannel; 2971 rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri, 2972 nsContentUtils::GetSystemPrincipal(), 2973 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 2974 nsIContentPolicy::TYPE_OTHER); 2975 NS_ENSURE_SUCCESS(rv, rv); 2976 2977 // Only allow local reading. 2978 nsCOMPtr<nsIURI> actualUri; 2979 rv = scriptChannel->GetURI(getter_AddRefs(actualUri)); 2980 NS_ENSURE_SUCCESS(rv, rv); 2981 nsCString scheme; 2982 rv = actualUri->GetScheme(scheme); 2983 NS_ENSURE_SUCCESS(rv, rv); 2984 if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) { 2985 return NS_OK; 2986 } 2987 2988 // Explicitly set the content type so that we don't load the 2989 // exthandler to guess it. 2990 scriptChannel->SetContentType("text/plain"_ns); 2991 2992 nsCOMPtr<nsIInputStream> scriptStream; 2993 rv = scriptChannel->Open(getter_AddRefs(scriptStream)); 2994 NS_ENSURE_SUCCESS(rv, rv); 2995 2996 uint64_t rawLen; 2997 rv = scriptStream->Available(&rawLen); 2998 NS_ENSURE_SUCCESS(rv, rv); 2999 if (!rawLen) { 3000 return NS_ERROR_FAILURE; 3001 } 3002 3003 // Technically, this should be SIZE_MAX, but we don't run on machines 3004 // where that would be less than UINT32_MAX, and the latter is already 3005 // well beyond a reasonable limit. 3006 if (rawLen > UINT32_MAX) { 3007 return NS_ERROR_FILE_TOO_BIG; 3008 } 3009 3010 // Allocate a buffer the size of the file to initially fill with the UTF-8 3011 // contents of the file. Use the JS allocator so that if UTF-8 source was 3012 // requested, we can return this memory directly. 3013 JS::UniqueChars buf(js_pod_malloc<char>(rawLen)); 3014 if (!buf) { 3015 return NS_ERROR_OUT_OF_MEMORY; 3016 } 3017 3018 char* ptr = buf.get(); 3019 char* end = ptr + rawLen; 3020 while (ptr < end) { 3021 uint32_t bytesRead; 3022 rv = scriptStream->Read(ptr, PointerRangeSize(ptr, end), &bytesRead); 3023 if (NS_FAILED(rv)) { 3024 return rv; 3025 } 3026 MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF"); 3027 ptr += bytesRead; 3028 } 3029 3030 if (utf8Source) { 3031 // |buf| is already UTF-8, so we can directly return it. 3032 *len = rawLen; 3033 *utf8Source = buf.release(); 3034 } else { 3035 MOZ_ASSERT(twoByteSource != nullptr); 3036 3037 // |buf| can't be directly returned -- convert it to UTF-16. 3038 3039 // On success this overwrites |chars| and |*len|. 3040 JS::UniqueTwoByteChars chars; 3041 rv = ScriptLoader::ConvertToUTF16( 3042 scriptChannel, reinterpret_cast<const unsigned char*>(buf.get()), 3043 rawLen, u"UTF-8"_ns, nullptr, chars, *len); 3044 NS_ENSURE_SUCCESS(rv, rv); 3045 3046 if (!chars) { 3047 return NS_ERROR_FAILURE; 3048 } 3049 3050 *twoByteSource = chars.release(); 3051 } 3052 3053 return NS_OK; 3054 } 3055 3056 // The JS engine calls this object's 'load' member function when it needs 3057 // the source for a chrome JS function. See the comment in the XPCJSRuntime 3058 // constructor. 3059 class XPCJSSourceHook : public js::SourceHook { 3060 bool load(JSContext* cx, const char* filename, char16_t** twoByteSource, 3061 char** utf8Source, size_t* length) override { 3062 MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), 3063 "must be called requesting only one of UTF-8 or UTF-16 source"); 3064 3065 *length = 0; 3066 if (twoByteSource) { 3067 *twoByteSource = nullptr; 3068 } else { 3069 *utf8Source = nullptr; 3070 } 3071 3072 if (!nsContentUtils::IsSystemCaller(cx)) { 3073 return true; 3074 } 3075 3076 if (!filename) { 3077 return true; 3078 } 3079 3080 nsresult rv = 3081 ReadSourceFromFilename(cx, filename, twoByteSource, utf8Source, length); 3082 if (NS_FAILED(rv)) { 3083 xpc::Throw(cx, rv); 3084 return false; 3085 } 3086 3087 return true; 3088 } 3089 }; 3090 3091 static const JSWrapObjectCallbacks WrapObjectCallbacks = { 3092 xpc::WrapperFactory::Rewrap, xpc::WrapperFactory::PrepareForWrapping}; 3093 3094 XPCJSRuntime::XPCJSRuntime(JSContext* aCx) 3095 : CycleCollectedJSRuntime(aCx), 3096 mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()), 3097 mIID2NativeInterfaceMap(mozilla::MakeUnique<IID2NativeInterfaceMap>()), 3098 mClassInfo2NativeSetMap(mozilla::MakeUnique<ClassInfo2NativeSetMap>()), 3099 mNativeSetMap(mozilla::MakeUnique<NativeSetMap>()), 3100 mGCIsRunning(false), 3101 mDoingFinalization(false), 3102 mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) { 3103 MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime); 3104 } 3105 3106 /* static */ 3107 XPCJSRuntime* XPCJSRuntime::Get() { return nsXPConnect::GetRuntimeInstance(); } 3108 3109 // Subclass of JS::ubi::Base for DOM reflector objects for the JS::ubi::Node 3110 // memory analysis framework; see js/public/UbiNode.h. In 3111 // XPCJSRuntime::Initialize, we register the ConstructUbiNode function as a hook 3112 // with the SpiderMonkey runtime for it to use to construct ubi::Nodes of this 3113 // class for JSObjects whose class has the JSCLASS_IS_DOMJSCLASS flag set. 3114 // ReflectorNode specializes Concrete<JSObject> for DOM reflector nodes, 3115 // reporting the edge from the JSObject to the nsINode it represents, in 3116 // addition to the usual edges departing any normal JSObject. 3117 namespace JS { 3118 namespace ubi { 3119 class ReflectorNode : public Concrete<JSObject> { 3120 protected: 3121 explicit ReflectorNode(JSObject* ptr) : Concrete<JSObject>(ptr) {} 3122 3123 public: 3124 static void construct(void* storage, JSObject* ptr) { 3125 new (storage) ReflectorNode(ptr); 3126 } 3127 js::UniquePtr<JS::ubi::EdgeRange> edges(JSContext* cx, 3128 bool wantNames) const override; 3129 }; 3130 3131 js::UniquePtr<EdgeRange> ReflectorNode::edges(JSContext* cx, 3132 bool wantNames) const { 3133 js::UniquePtr<SimpleEdgeRange> range(static_cast<SimpleEdgeRange*>( 3134 Concrete<JSObject>::edges(cx, wantNames).release())); 3135 if (!range) { 3136 return nullptr; 3137 } 3138 // UNWRAP_NON_WRAPPER_OBJECT assumes the object is completely initialized, 3139 // but ours may not be. Luckily, UnwrapDOMObjectToISupports checks for the 3140 // uninitialized case (and returns null if uninitialized), so we can use that 3141 // to guard against uninitialized objects. 3142 nsISupports* supp = UnwrapDOMObjectToISupports(&get()); 3143 if (supp) { 3144 JS::AutoSuppressGCAnalysis nogc; // bug 1582326 3145 3146 nsINode* node; 3147 // UnwrapDOMObjectToISupports can only return non-null if its argument is 3148 // an actual DOM object, not a cross-compartment wrapper. 3149 if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Node, &get(), node))) { 3150 char16_t* edgeName = nullptr; 3151 if (wantNames) { 3152 edgeName = NS_xstrdup(u"Reflected Node"); 3153 } 3154 if (!range->addEdge(Edge(edgeName, node))) { 3155 return nullptr; 3156 } 3157 } 3158 } 3159 return js::UniquePtr<EdgeRange>(range.release()); 3160 } 3161 3162 } // Namespace ubi 3163 } // Namespace JS 3164 3165 void ConstructUbiNode(void* storage, JSObject* ptr) { 3166 JS::ubi::ReflectorNode::construct(storage, ptr); 3167 } 3168 3169 void XPCJSRuntime::Initialize(JSContext* cx) { 3170 // these jsids filled in later when we have a JSContext to work with. 3171 mStrIDs[0] = JS::PropertyKey::Void(); 3172 3173 nsScriptSecurityManager::GetScriptSecurityManager()->InitJSCallbacks(cx); 3174 3175 // Unconstrain the runtime's threshold on nominal heap size, to avoid 3176 // triggering GC too often if operating continuously near an arbitrary 3177 // finite threshold (0xffffffff is infinity for uint32_t parameters). 3178 // This leaves the maximum-JS_malloc-bytes threshold still in effect 3179 // to cause period, and we hope hygienic, last-ditch GCs from within 3180 // the GC's allocator. 3181 JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff); 3182 3183 JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback); 3184 JS_SetSizeOfIncludingThisCompartmentCallback( 3185 cx, CompartmentSizeOfIncludingThisCallback); 3186 JS::SetDestroyRealmCallback(cx, DestroyRealm); 3187 JS::SetRealmNameCallback(cx, GetRealmNameCallback); 3188 mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback); 3189 mPrevDoCycleCollectionCallback = 3190 JS::SetDoCycleCollectionCallback(cx, DoCycleCollectionCallback); 3191 JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr); 3192 JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this); 3193 JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, 3194 this); 3195 JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); 3196 if (XRE_IsE10sParentProcess()) { 3197 JS::SetFilenameValidationCallback( 3198 nsContentSecurityUtils::ValidateScriptFilename); 3199 } 3200 js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper); 3201 JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals); 3202 JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback); 3203 JS_SetSetUseCounterCallback(cx, SetUseCounterCallback); 3204 3205 js::SetWindowProxyClass(cx, &OuterWindowProxyClass); 3206 3207 JS::SetXrayJitInfo(&gXrayJitInfo); 3208 JS::SetProcessLargeAllocationFailureCallback( 3209 OnLargeAllocationFailureCallback); 3210 3211 // The WasmAltDataType is build by the JS engine from the build id. 3212 JS::SetProcessBuildIdOp(GetBuildId); 3213 FetchUtil::InitWasmAltDataType(); 3214 3215 // The JS engine needs to keep the source code around in order to implement 3216 // Function.prototype.toSource(). It'd be nice to not have to do this for 3217 // chrome code and simply stub out requests for source on it. Life is not so 3218 // easy, unfortunately. Nobody relies on chrome toSource() working in core 3219 // browser code, but chrome tests use it. The worst offenders are addons, 3220 // which like to monkeypatch chrome functions by calling toSource() on them 3221 // and using regular expressions to modify them. We avoid keeping most browser 3222 // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when 3223 // compiling some chrome code. This causes the JS engine not save the source 3224 // code in memory. When the JS engine is asked to provide the source for a 3225 // function compiled with LAZY_SOURCE, it calls SourceHook to load it. 3226 /// 3227 // Note we do have to retain the source code in memory for scripts compiled in 3228 // isRunOnce mode and compiled function bodies (from 3229 // JS::CompileFunction). In practice, this means content scripts and event 3230 // handlers. 3231 mozilla::UniquePtr<XPCJSSourceHook> hook(new XPCJSSourceHook); 3232 js::SetSourceHook(cx, std::move(hook)); 3233 3234 // Register memory reporters and distinguished amount functions. 3235 RegisterStrongMemoryReporter(new JSMainRuntimeRealmsReporter()); 3236 RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter()); 3237 RegisterJSMainRuntimeGCHeapDistinguishedAmount( 3238 JSMainRuntimeGCHeapDistinguishedAmount); 3239 RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount( 3240 JSMainRuntimeTemporaryPeakDistinguishedAmount); 3241 RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount( 3242 JSMainRuntimeCompartmentsSystemDistinguishedAmount); 3243 RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount( 3244 JSMainRuntimeCompartmentsUserDistinguishedAmount); 3245 RegisterJSMainRuntimeRealmsSystemDistinguishedAmount( 3246 JSMainRuntimeRealmsSystemDistinguishedAmount); 3247 RegisterJSMainRuntimeRealmsUserDistinguishedAmount( 3248 JSMainRuntimeRealmsUserDistinguishedAmount); 3249 mozilla::RegisterJSSizeOfTab(JSSizeOfTab); 3250 3251 // Set the callback for reporting memory to ubi::Node. 3252 JS::ubi::SetConstructUbiNodeForDOMObjectCallback(cx, &ConstructUbiNode); 3253 3254 xpc_LocalizeRuntime(JS_GetRuntime(cx)); 3255 } 3256 3257 bool XPCJSRuntime::InitializeStrings(JSContext* cx) { 3258 // if it is our first context then we need to generate our string ids 3259 if (mStrIDs[0].isVoid()) { 3260 RootedString str(cx); 3261 for (unsigned i = 0; i < XPCJSContext::IDX_TOTAL_COUNT; i++) { 3262 str = JS_AtomizeAndPinString(cx, mStrings[i]); 3263 if (!str) { 3264 mStrIDs[0] = JS::PropertyKey::Void(); 3265 return false; 3266 } 3267 mStrIDs[i] = PropertyKey::fromPinnedString(str); 3268 } 3269 3270 if (!mozilla::dom::DefineStaticJSVals(cx)) { 3271 return false; 3272 } 3273 } 3274 3275 return true; 3276 } 3277 3278 bool XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const JSClass* clasp, 3279 char (&name)[72]) const { 3280 if (clasp != &XPC_WN_Proto_JSClass) { 3281 return false; 3282 } 3283 3284 XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); 3285 // Nothing here can GC. The analysis would otherwise think that ~nsCOMPtr 3286 // could GC, but that's only possible if nsIXPCScriptable::GetJSClass() 3287 // somehow released a reference to the nsIXPCScriptable, which isn't going to 3288 // happen. 3289 JS::AutoSuppressGCAnalysis nogc; 3290 nsCOMPtr<nsIXPCScriptable> scr = p->GetScriptable(); 3291 if (!scr) { 3292 return false; 3293 } 3294 3295 SprintfLiteral(name, "JS Object (%s - %s)", clasp->name, 3296 scr->GetJSClass()->name); 3297 return true; 3298 } 3299 3300 bool XPCJSRuntime::NoteCustomGCThingXPCOMChildren( 3301 const JSClass* clasp, JSObject* obj, 3302 nsCycleCollectionTraversalCallback& cb) const { 3303 if (clasp != &XPC_WN_Tearoff_JSClass) { 3304 return false; 3305 } 3306 3307 // A tearoff holds a strong reference to its native object 3308 // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative 3309 // will be held alive through tearoff's XPC_WN_TEAROFF_FLAT_OBJECT_SLOT, 3310 // which points to the XPCWrappedNative's mFlatJSObject. 3311 XPCWrappedNativeTearOff* to = XPCWrappedNativeTearOff::Get(obj); 3312 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( 3313 cb, "XPCWrappedNativeTearOff::Get(obj)->mNative"); 3314 cb.NoteXPCOMChild(to->GetNative()); 3315 return true; 3316 } 3317 3318 /***************************************************************************/ 3319 3320 void XPCJSRuntime::DebugDump(int16_t depth) { 3321 #ifdef DEBUG 3322 depth--; 3323 XPC_LOG_ALWAYS(("XPCJSRuntime @ %p", this)); 3324 XPC_LOG_INDENT(); 3325 3326 // iterate wrappers... 3327 XPC_LOG_ALWAYS(("mWrappedJSMap @ %p with %d wrappers(s)", mWrappedJSMap.get(), 3328 mWrappedJSMap->Count())); 3329 if (depth && mWrappedJSMap->Count()) { 3330 XPC_LOG_INDENT(); 3331 mWrappedJSMap->Dump(depth); 3332 XPC_LOG_OUTDENT(); 3333 } 3334 3335 XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %p with %d interface(s)", 3336 mIID2NativeInterfaceMap.get(), 3337 mIID2NativeInterfaceMap->Count())); 3338 3339 XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %p with %d sets(s)", 3340 mClassInfo2NativeSetMap.get(), 3341 mClassInfo2NativeSetMap->Count())); 3342 3343 XPC_LOG_ALWAYS(("mNativeSetMap @ %p with %d sets(s)", mNativeSetMap.get(), 3344 mNativeSetMap->Count())); 3345 3346 // iterate sets... 3347 if (depth && mNativeSetMap->Count()) { 3348 XPC_LOG_INDENT(); 3349 for (auto i = mNativeSetMap->Iter(); !i.done(); i.next()) { 3350 i.get()->DebugDump(depth); 3351 } 3352 XPC_LOG_OUTDENT(); 3353 } 3354 3355 XPC_LOG_OUTDENT(); 3356 #endif 3357 } 3358 3359 /***************************************************************************/ 3360 3361 void XPCJSRuntime::AddGCCallback(xpcGCCallback cb) { 3362 MOZ_ASSERT(cb, "null callback"); 3363 extraGCCallbacks.AppendElement(cb); 3364 } 3365 3366 void XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) { 3367 MOZ_ASSERT(cb, "null callback"); 3368 bool found = extraGCCallbacks.RemoveElement(cb); 3369 if (!found) { 3370 NS_ERROR("Removing a callback which was never added."); 3371 } 3372 } 3373 3374 JSObject* XPCJSRuntime::GetUAWidgetScope(JSContext* cx, 3375 nsIPrincipal* principal) { 3376 MOZ_ASSERT(!principal->IsSystemPrincipal(), "Running UA Widget in chrome"); 3377 3378 RootedObject scope(cx); 3379 do { 3380 RefPtr<BasePrincipal> key = BasePrincipal::Cast(principal); 3381 if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) { 3382 scope = p->value(); 3383 break; // Need ~RefPtr to run, and potentially GC, before returning. 3384 } 3385 3386 SandboxOptions options; 3387 options.sandboxName.AssignLiteral("UA Widget Scope"); 3388 options.wantXrays = false; 3389 options.wantComponents = false; 3390 options.isUAWidgetScope = true; 3391 3392 // Use an ExpandedPrincipal to create asymmetric security. 3393 MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal)); 3394 nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray{principal}; 3395 RefPtr<ExpandedPrincipal> ep = ExpandedPrincipal::Create( 3396 principalAsArray, principal->OriginAttributesRef()); 3397 3398 // Create the sandbox. 3399 RootedValue v(cx); 3400 nsresult rv = CreateSandboxObject( 3401 cx, &v, static_cast<nsIExpandedPrincipal*>(ep), options); 3402 NS_ENSURE_SUCCESS(rv, nullptr); 3403 scope = &v.toObject(); 3404 3405 JSObject* unwrapped = js::UncheckedUnwrap(scope); 3406 MOZ_ASSERT(xpc::IsInUAWidgetScope(unwrapped)); 3407 3408 MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, unwrapped)); 3409 } while (false); 3410 3411 return scope; 3412 } 3413 3414 JSObject* XPCJSRuntime::UnprivilegedJunkScope(const mozilla::fallible_t&) { 3415 if (!mUnprivilegedJunkScope) { 3416 dom::AutoJSAPI jsapi; 3417 jsapi.Init(); 3418 JSContext* cx = jsapi.cx(); 3419 3420 SandboxOptions options; 3421 options.sandboxName.AssignLiteral("XPConnect Junk Compartment"); 3422 options.invisibleToDebugger = true; 3423 3424 RootedValue sandbox(cx); 3425 nsresult rv = CreateSandboxObject(cx, &sandbox, nullptr, options); 3426 NS_ENSURE_SUCCESS(rv, nullptr); 3427 3428 mUnprivilegedJunkScope = 3429 SandboxPrivate::GetPrivate(sandbox.toObjectOrNull()); 3430 } 3431 MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(), 3432 "Wrapper should have same lifetime as weak reference"); 3433 return mUnprivilegedJunkScope->GetWrapper(); 3434 } 3435 3436 JSObject* XPCJSRuntime::UnprivilegedJunkScope() { 3437 JSObject* scope = UnprivilegedJunkScope(fallible); 3438 MOZ_RELEASE_ASSERT(scope); 3439 return scope; 3440 } 3441 3442 bool XPCJSRuntime::IsUnprivilegedJunkScope(JSObject* obj) { 3443 return mUnprivilegedJunkScope && obj == mUnprivilegedJunkScope->GetWrapper(); 3444 } 3445 3446 void XPCJSRuntime::DeleteSingletonScopes() { 3447 // We're pretty late in shutdown, so we call ReleaseWrapper on the scopes. 3448 // This way the GC can collect them immediately, and we don't rely on the CC 3449 // to clean up. 3450 if (RefPtr<SandboxPrivate> sandbox = mUnprivilegedJunkScope.get()) { 3451 sandbox->ReleaseWrapper(sandbox); 3452 mUnprivilegedJunkScope = nullptr; 3453 } 3454 } 3455 3456 uint32_t GetAndClampCPUCount() { 3457 // See HelperThreads.cpp for why we want between 2-8 threads 3458 int32_t proc = GetNumberOfProcessors(); 3459 if (proc < 2) { 3460 return 2; 3461 } 3462 return std::min(proc, 8); 3463 }