summaryrefslogtreecommitdiffstats
path: root/xpcom/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /xpcom/base
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/base')
-rw-r--r--xpcom/base/AppShutdown.cpp469
-rw-r--r--xpcom/base/AppShutdown.h152
-rw-r--r--xpcom/base/AutoRestore.h42
-rw-r--r--xpcom/base/AvailableMemoryTracker.cpp189
-rw-r--r--xpcom/base/AvailableMemoryTracker.h25
-rw-r--r--xpcom/base/AvailableMemoryWatcher.cpp186
-rw-r--r--xpcom/base/AvailableMemoryWatcher.h78
-rw-r--r--xpcom/base/AvailableMemoryWatcherLinux.cpp282
-rw-r--r--xpcom/base/AvailableMemoryWatcherMac.cpp634
-rw-r--r--xpcom/base/AvailableMemoryWatcherUtils.h56
-rw-r--r--xpcom/base/AvailableMemoryWatcherWin.cpp401
-rw-r--r--xpcom/base/ClearOnShutdown.cpp63
-rw-r--r--xpcom/base/ClearOnShutdown.h147
-rw-r--r--xpcom/base/CodeAddressService.h250
-rw-r--r--xpcom/base/CountingAllocatorBase.h158
-rw-r--r--xpcom/base/CycleCollectedJSContext.cpp925
-rw-r--r--xpcom/base/CycleCollectedJSContext.h396
-rw-r--r--xpcom/base/CycleCollectedJSRuntime.cpp2027
-rw-r--r--xpcom/base/CycleCollectedJSRuntime.h526
-rw-r--r--xpcom/base/Debug.cpp21
-rw-r--r--xpcom/base/Debug.h21
-rw-r--r--xpcom/base/DebuggerOnGCRunnable.cpp46
-rw-r--r--xpcom/base/DebuggerOnGCRunnable.h35
-rw-r--r--xpcom/base/DeferredFinalize.cpp23
-rw-r--r--xpcom/base/DeferredFinalize.h35
-rw-r--r--xpcom/base/EnumeratedArrayCycleCollection.h32
-rwxr-xr-xxpcom/base/ErrorList.py1425
-rw-r--r--xpcom/base/ErrorNames.cpp78
-rw-r--r--xpcom/base/ErrorNames.h29
-rw-r--r--xpcom/base/GkRustUtils.cpp16
-rw-r--r--xpcom/base/GkRustUtils.h18
-rw-r--r--xpcom/base/HoldDropJSObjects.cpp53
-rw-r--r--xpcom/base/HoldDropJSObjects.h84
-rw-r--r--xpcom/base/IntentionalCrash.h71
-rw-r--r--xpcom/base/JSONStringWriteFuncs.h52
-rw-r--r--xpcom/base/JSObjectHolder.cpp9
-rw-r--r--xpcom/base/JSObjectHolder.h42
-rw-r--r--xpcom/base/LogCommandLineHandler.cpp90
-rw-r--r--xpcom/base/LogCommandLineHandler.h49
-rw-r--r--xpcom/base/LogModulePrefWatcher.cpp164
-rw-r--r--xpcom/base/LogModulePrefWatcher.h39
-rw-r--r--xpcom/base/Logging.cpp913
-rw-r--r--xpcom/base/Logging.h322
-rw-r--r--xpcom/base/MacHelpers.h18
-rw-r--r--xpcom/base/MacHelpers.mm33
-rw-r--r--xpcom/base/MacStringHelpers.h20
-rw-r--r--xpcom/base/MacStringHelpers.mm35
-rw-r--r--xpcom/base/MemoryInfo.cpp105
-rw-r--r--xpcom/base/MemoryInfo.h81
-rw-r--r--xpcom/base/MemoryMapping.cpp207
-rw-r--r--xpcom/base/MemoryMapping.h183
-rw-r--r--xpcom/base/MemoryPressureLevelMac.h77
-rw-r--r--xpcom/base/MemoryReportingProcess.h45
-rw-r--r--xpcom/base/MemoryTelemetry.cpp526
-rw-r--r--xpcom/base/MemoryTelemetry.h72
-rw-r--r--xpcom/base/NSPRLogModulesParser.cpp66
-rw-r--r--xpcom/base/NSPRLogModulesParser.h24
-rw-r--r--xpcom/base/OwningNonNull.h213
-rw-r--r--xpcom/base/PHCManager.cpp89
-rw-r--r--xpcom/base/PHCManager.h20
-rw-r--r--xpcom/base/RLBoxSandboxPool.cpp111
-rw-r--r--xpcom/base/RLBoxSandboxPool.h105
-rw-r--r--xpcom/base/RLBoxUtils.h70
-rw-r--r--xpcom/base/ShutdownPhase.h32
-rw-r--r--xpcom/base/SizeOfState.h67
-rw-r--r--xpcom/base/StaticLocalPtr.h253
-rw-r--r--xpcom/base/StaticMonitor.h117
-rw-r--r--xpcom/base/StaticMutex.h84
-rw-r--r--xpcom/base/StaticPtr.h235
-rw-r--r--xpcom/base/components.conf24
-rw-r--r--xpcom/base/moz.build271
-rw-r--r--xpcom/base/nsAlgorithm.h54
-rw-r--r--xpcom/base/nsAutoRef.h489
-rw-r--r--xpcom/base/nsCOMPtr.cpp34
-rw-r--r--xpcom/base/nsCOMPtr.h1174
-rw-r--r--xpcom/base/nsCRTGlue.cpp226
-rw-r--r--xpcom/base/nsCRTGlue.h172
-rw-r--r--xpcom/base/nsClassInfoImpl.cpp61
-rw-r--r--xpcom/base/nsCom.h10
-rw-r--r--xpcom/base/nsConsoleMessage.cpp67
-rw-r--r--xpcom/base/nsConsoleMessage.h31
-rw-r--r--xpcom/base/nsConsoleService.cpp568
-rw-r--r--xpcom/base/nsConsoleService.h113
-rw-r--r--xpcom/base/nsCrashOnException.cpp34
-rw-r--r--xpcom/base/nsCrashOnException.h23
-rw-r--r--xpcom/base/nsCycleCollectionNoteChild.h85
-rw-r--r--xpcom/base/nsCycleCollectionNoteRootCallback.h44
-rw-r--r--xpcom/base/nsCycleCollectionParticipant.cpp34
-rw-r--r--xpcom/base/nsCycleCollectionParticipant.h1107
-rw-r--r--xpcom/base/nsCycleCollectionTraversalCallback.h70
-rw-r--r--xpcom/base/nsCycleCollector.cpp4042
-rw-r--r--xpcom/base/nsCycleCollector.h74
-rw-r--r--xpcom/base/nsCycleCollectorTraceJSHelpers.cpp89
-rw-r--r--xpcom/base/nsDebug.h330
-rw-r--r--xpcom/base/nsDebugImpl.cpp675
-rw-r--r--xpcom/base/nsDebugImpl.h42
-rw-r--r--xpcom/base/nsDumpUtils.cpp488
-rw-r--r--xpcom/base/nsDumpUtils.h184
-rw-r--r--xpcom/base/nsError.h90
-rw-r--r--xpcom/base/nsGZFileWriter.cpp97
-rw-r--r--xpcom/base/nsGZFileWriter.h79
-rw-r--r--xpcom/base/nsIAvailableMemoryWatcherBase.idl36
-rw-r--r--xpcom/base/nsIClassInfoImpl.h193
-rw-r--r--xpcom/base/nsIConsoleListener.idl18
-rw-r--r--xpcom/base/nsIConsoleMessage.idl52
-rw-r--r--xpcom/base/nsIConsoleService.idl78
-rw-r--r--xpcom/base/nsICycleCollectorListener.idl166
-rw-r--r--xpcom/base/nsID.cpp239
-rw-r--r--xpcom/base/nsID.h173
-rw-r--r--xpcom/base/nsIDUtils.h32
-rw-r--r--xpcom/base/nsIDebug2.idl100
-rw-r--r--xpcom/base/nsIException.idl80
-rw-r--r--xpcom/base/nsIInterfaceRequestor.idl35
-rw-r--r--xpcom/base/nsIInterfaceRequestorUtils.cpp32
-rw-r--r--xpcom/base/nsIInterfaceRequestorUtils.h40
-rw-r--r--xpcom/base/nsIMacPreferencesReader.idl34
-rw-r--r--xpcom/base/nsIMemoryInfoDumper.idl167
-rw-r--r--xpcom/base/nsIMemoryReporter.idl615
-rw-r--r--xpcom/base/nsINIParser.cpp325
-rw-r--r--xpcom/base/nsINIParser.h168
-rw-r--r--xpcom/base/nsISecurityConsoleMessage.idl20
-rw-r--r--xpcom/base/nsISizeOf.h39
-rw-r--r--xpcom/base/nsISupports.idl60
-rw-r--r--xpcom/base/nsISupportsImpl.cpp144
-rw-r--r--xpcom/base/nsISupportsImpl.h1484
-rw-r--r--xpcom/base/nsISupportsUtils.h142
-rw-r--r--xpcom/base/nsIUUIDGenerator.idl39
-rw-r--r--xpcom/base/nsIVersionComparator.idl48
-rw-r--r--xpcom/base/nsIWeakReference.idl114
-rw-r--r--xpcom/base/nsIWeakReferenceUtils.h84
-rw-r--r--xpcom/base/nsInterfaceRequestorAgg.cpp74
-rw-r--r--xpcom/base/nsInterfaceRequestorAgg.h35
-rw-r--r--xpcom/base/nsMacPreferencesReader.h34
-rw-r--r--xpcom/base/nsMacPreferencesReader.mm87
-rw-r--r--xpcom/base/nsMacUtilsImpl.cpp544
-rw-r--r--xpcom/base/nsMacUtilsImpl.h55
-rw-r--r--xpcom/base/nsMaybeWeakPtr.h169
-rw-r--r--xpcom/base/nsMemory.h62
-rw-r--r--xpcom/base/nsMemoryImpl.cpp131
-rw-r--r--xpcom/base/nsMemoryInfoDumper.cpp742
-rw-r--r--xpcom/base/nsMemoryInfoDumper.h48
-rw-r--r--xpcom/base/nsMemoryReporterManager.cpp2923
-rw-r--r--xpcom/base/nsMemoryReporterManager.h321
-rw-r--r--xpcom/base/nsObjCExceptions.h56
-rw-r--r--xpcom/base/nsObjCExceptions.mm51
-rw-r--r--xpcom/base/nsQueryObject.h93
-rw-r--r--xpcom/base/nsSecurityConsoleMessage.cpp37
-rw-r--r--xpcom/base/nsSecurityConsoleMessage.h33
-rw-r--r--xpcom/base/nsSystemInfo.cpp1683
-rw-r--r--xpcom/base/nsSystemInfo.h130
-rw-r--r--xpcom/base/nsTraceRefcnt.cpp1219
-rw-r--r--xpcom/base/nsTraceRefcnt.h38
-rw-r--r--xpcom/base/nsUUIDGenerator.cpp30
-rw-r--r--xpcom/base/nsUUIDGenerator.h30
-rw-r--r--xpcom/base/nsVersionComparator.cpp401
-rw-r--r--xpcom/base/nsVersionComparator.h112
-rw-r--r--xpcom/base/nsVersionComparatorImpl.cpp20
-rw-r--r--xpcom/base/nsVersionComparatorImpl.h28
-rw-r--r--xpcom/base/nsWeakReference.cpp151
-rw-r--r--xpcom/base/nsWeakReference.h68
-rw-r--r--xpcom/base/nsWindowsHelpers.h339
-rw-r--r--xpcom/base/nscore.h257
-rw-r--r--xpcom/base/nsrootidl.idl105
163 files changed, 39305 insertions, 0 deletions
diff --git a/xpcom/base/AppShutdown.cpp b/xpcom/base/AppShutdown.cpp
new file mode 100644
index 0000000000..c68dedef31
--- /dev/null
+++ b/xpcom/base/AppShutdown.cpp
@@ -0,0 +1,469 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ShutdownPhase.h"
+#ifdef XP_WIN
+# include <windows.h>
+# include "mozilla/PreXULSkeletonUI.h"
+#else
+# include <unistd.h>
+#endif
+
+#include "ProfilerControl.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/Printf.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StartupTimeline.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+#include "mozilla/LateWriteChecks.h"
+#include "mozilla/Services.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsAppRunner.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsICertStorage.h"
+#include "nsThreadUtils.h"
+
+#include "AppShutdown.h"
+
+// TODO: understand why on Android we cannot include this and if we should
+#ifndef ANDROID
+# include "nsTerminator.h"
+#endif
+#include "prenv.h"
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+
+namespace mozilla {
+
+const char* sPhaseObserverKeys[] = {
+ nullptr, // NotInShutdown
+ "quit-application", // AppShutdownConfirmed
+ "profile-change-net-teardown", // AppShutdownNetTeardown
+ "profile-change-teardown", // AppShutdownTeardown
+ "profile-before-change", // AppShutdown
+ "profile-before-change-qm", // AppShutdownQM
+ "profile-before-change-telemetry", // AppShutdownTelemetry
+ "xpcom-will-shutdown", // XPCOMWillShutdown
+ "xpcom-shutdown", // XPCOMShutdown
+ "xpcom-shutdown-threads", // XPCOMShutdownThreads
+ nullptr, // XPCOMShutdownFinal
+ nullptr // CCPostLastCycleCollection
+};
+
+static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) ==
+ (size_t)ShutdownPhase::ShutdownPhase_Length);
+
+const char* sPhaseReadableNames[] = {"NotInShutdown",
+ "AppShutdownConfirmed",
+ "AppShutdownNetTeardown",
+ "AppShutdownTeardown",
+ "AppShutdown",
+ "AppShutdownQM",
+ "AppShutdownTelemetry",
+ "XPCOMWillShutdown",
+ "XPCOMShutdown",
+ "XPCOMShutdownThreads",
+ "XPCOMShutdownFinal",
+ "CCPostLastCycleCollection"};
+
+static_assert(sizeof(sPhaseReadableNames) / sizeof(sPhaseReadableNames[0]) ==
+ (size_t)ShutdownPhase::ShutdownPhase_Length);
+
+#ifndef ANDROID
+static nsTerminator* sTerminator = nullptr;
+#endif
+
+static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown;
+static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown;
+static AppShutdownMode sShutdownMode = AppShutdownMode::Normal;
+static Atomic<ShutdownPhase> sCurrentShutdownPhase(
+ ShutdownPhase::NotInShutdown);
+static int sExitCode = 0;
+
+// These environment variable strings are all deliberately copied and leaked
+// due to requirements of PR_SetEnv and similar.
+static char* sSavedXulAppFile = nullptr;
+#ifdef XP_WIN
+static wchar_t* sSavedProfDEnvVar = nullptr;
+static wchar_t* sSavedProfLDEnvVar = nullptr;
+#else
+static char* sSavedProfDEnvVar = nullptr;
+static char* sSavedProfLDEnvVar = nullptr;
+#endif
+
+ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
+ switch (aPrefValue) {
+ case 1:
+ return ShutdownPhase::CCPostLastCycleCollection;
+ case 2:
+ return ShutdownPhase::XPCOMShutdownThreads;
+ case 3:
+ return ShutdownPhase::XPCOMShutdown;
+ // NOTE: the remaining values from the ShutdownPhase enum will be added
+ // when we're at least reasonably confident that the world won't come
+ // crashing down if we do a fast shutdown at that point.
+ }
+ return ShutdownPhase::NotInShutdown;
+}
+
+ShutdownPhase AppShutdown::GetCurrentShutdownPhase() {
+ return sCurrentShutdownPhase;
+}
+
+bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) {
+ return (sCurrentShutdownPhase >= aPhase);
+}
+
+int AppShutdown::GetExitCode() { return sExitCode; }
+
+void AppShutdown::SaveEnvVarsForPotentialRestart() {
+ const char* s = PR_GetEnv("XUL_APP_FILE");
+ if (s) {
+ sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release();
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile);
+ }
+}
+
+const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) {
+ return sPhaseObserverKeys[static_cast<std::underlying_type_t<ShutdownPhase>>(
+ aPhase)];
+}
+
+const char* AppShutdown::GetShutdownPhaseName(ShutdownPhase aPhase) {
+ return sPhaseReadableNames[static_cast<std::underlying_type_t<ShutdownPhase>>(
+ aPhase)];
+}
+
+void AppShutdown::MaybeDoRestart() {
+ if (sShutdownMode == AppShutdownMode::Restart) {
+ StopLateWriteChecks();
+
+ // Since we'll be launching our child while we're still alive, make sure
+ // we've unlocked the profile first, otherwise the child could hit its
+ // profile lock check before we've exited and thus released our lock.
+ UnlockProfile();
+
+ if (sSavedXulAppFile) {
+ PR_SetEnv(sSavedXulAppFile);
+ }
+
+#ifdef XP_WIN
+ if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
+ SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar);
+ }
+ if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
+ SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar);
+ }
+ Unused << NotePreXULSkeletonUIRestarting();
+#else
+ if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) {
+ PR_SetEnv(sSavedProfDEnvVar);
+ }
+ if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) {
+ PR_SetEnv(sSavedProfLDEnvVar);
+ }
+#endif
+
+ LaunchChild(true);
+ }
+}
+
+#ifdef XP_WIN
+wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) {
+ wchar_t* result = nullptr;
+ nsAutoString resStr;
+ aFile->GetPath(resStr);
+ if (resStr.Length() > 0) {
+ result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t));
+ if (result) {
+ wcscpy(result, resStr.get());
+ result[resStr.Length()] = 0;
+ }
+ }
+
+ return result;
+}
+#endif
+
+void AppShutdown::Init(AppShutdownMode aMode, int aExitCode,
+ AppShutdownReason aReason) {
+ if (sShutdownMode == AppShutdownMode::Normal) {
+ sShutdownMode = aMode;
+ }
+ AppShutdown::AnnotateShutdownReason(aReason);
+
+ sExitCode = aExitCode;
+
+#ifndef ANDROID
+ sTerminator = new nsTerminator();
+#endif
+
+ // Late-write checks needs to find the profile directory, so it has to
+ // be initialized before services::Shutdown or (because of
+ // xpcshell tests replacing the service) modules being unloaded.
+ InitLateWriteChecks();
+
+ int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage();
+ sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref);
+ int32_t lateWriteChecksPref =
+ StaticPrefs::toolkit_shutdown_lateWriteChecksStage();
+ sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref);
+
+ // Very early shutdowns can happen before the startup cache is even
+ // initialized; don't bother initializing it during shutdown.
+ if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
+ cache->MaybeInitShutdownWrite();
+ }
+}
+
+void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
+ // For writes which we want to ensure are recorded, we don't want to trip
+ // the late write checking code. Anything that writes to disk and which
+ // we don't want to skip should be listed out explicitly in this section.
+ if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) {
+ if (auto* cache = scache::StartupCache::GetSingletonNoInit()) {
+ cache->EnsureShutdownWriteComplete();
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICertStorage> certStorage =
+ do_GetService("@mozilla.org/security/certstorage;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ SpinEventLoopUntil("AppShutdown::MaybeFastShutdown"_ns, [&]() {
+ int32_t remainingOps;
+ nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "nsICertStorage::getRemainingOperationCount failed during "
+ "shutdown");
+ return NS_FAILED(rv) || remainingOps <= 0;
+ });
+ }
+ }
+ if (aPhase == sFastShutdownPhase) {
+ StopLateWriteChecks();
+ RecordShutdownEndTimeStamp();
+ MaybeDoRestart();
+
+ profiler_shutdown(IsFastShutdown::Yes);
+
+ DoImmediateExit(sExitCode);
+ } else if (aPhase == sLateWriteChecksPhase) {
+#ifdef XP_MACOSX
+ OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
+ BeginLateWriteChecks();
+ }
+}
+
+void AppShutdown::OnShutdownConfirmed() {
+ // If we're restarting, we need to save environment variables correctly
+ // while everything is still alive to do so.
+ if (sShutdownMode == AppShutdownMode::Restart) {
+ nsCOMPtr<nsIFile> profD;
+ nsCOMPtr<nsIFile> profLD;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(profLD));
+#ifdef XP_WIN
+ sSavedProfDEnvVar = CopyPathIntoNewWCString(profD);
+ sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD);
+#else
+ nsAutoCString profDStr;
+ profD->GetNativePath(profDStr);
+ sSavedProfDEnvVar =
+ Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release();
+ nsAutoCString profLDStr;
+ profLD->GetNativePath(profLDStr);
+ sSavedProfLDEnvVar =
+ Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release();
+#endif
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar);
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar);
+ }
+}
+
+void AppShutdown::DoImmediateExit(int aExitCode) {
+#ifdef XP_WIN
+ HANDLE process = ::GetCurrentProcess();
+ if (::TerminateProcess(process, aExitCode)) {
+ ::WaitForSingleObject(process, INFINITE);
+ }
+ MOZ_CRASH("TerminateProcess failed.");
+#else
+ _exit(aExitCode);
+#endif
+}
+
+bool AppShutdown::IsRestarting() {
+ return sShutdownMode == AppShutdownMode::Restart;
+}
+
+void AppShutdown::AnnotateShutdownReason(AppShutdownReason aReason) {
+ auto key = CrashReporter::Annotation::ShutdownReason;
+ nsCString reasonStr;
+ switch (aReason) {
+ case AppShutdownReason::AppClose:
+ reasonStr = "AppClose"_ns;
+ break;
+ case AppShutdownReason::AppRestart:
+ reasonStr = "AppRestart"_ns;
+ break;
+ case AppShutdownReason::OSForceClose:
+ reasonStr = "OSForceClose"_ns;
+ break;
+ case AppShutdownReason::OSSessionEnd:
+ reasonStr = "OSSessionEnd"_ns;
+ break;
+ case AppShutdownReason::OSShutdown:
+ reasonStr = "OSShutdown"_ns;
+ break;
+ case AppShutdownReason::WinUnexpectedMozQuit:
+ reasonStr = "WinUnexpectedMozQuit"_ns;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should know the given reason for shutdown.");
+ reasonStr = "Unknown"_ns;
+ break;
+ }
+ CrashReporter::AnnotateCrashReport(key, reasonStr);
+}
+
+#ifdef DEBUG
+static bool sNotifyingShutdownObservers = false;
+static bool sAdvancingShutdownPhase = false;
+
+bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) {
+ if (!XRE_IsParentProcess()) {
+ // Until we know what to do with AppShutdown for child processes,
+ // we ignore them for now. See bug 1697745.
+ return true;
+ }
+ ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic);
+ return phase == ShutdownPhase::NotInShutdown ||
+ (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase);
+}
+#endif
+
+void AppShutdown::AdvanceShutdownPhaseInternal(
+ ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData,
+ const nsCOMPtr<nsISupports>& aNotificationSubject) {
+ AssertIsOnMainThread();
+#ifdef DEBUG
+ // Prevent us from re-entrance
+ MOZ_ASSERT(!sAdvancingShutdownPhase);
+ sAdvancingShutdownPhase = true;
+ auto exit = MakeScopeExit([] { sAdvancingShutdownPhase = false; });
+#endif
+
+ // We ensure that we can move only forward. We cannot
+ // MOZ_ASSERT here as there are some tests that fire
+ // notifications out of shutdown order.
+ // See for example test_sss_sanitizeOnShutdown.js
+ if (sCurrentShutdownPhase >= aPhase) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ // AppShutdownConfirmed is special in some ways as
+ // - we can be called on top of a nested event loop (and it is the phase for
+ // which SpinEventLoopUntilOrQuit breaks, so we want to return soon)
+ // - we can be called from a sync marionette function that wants immediate
+ // feedback, too
+ // - in general, AppShutdownConfirmed will fire the "quit-application"
+ // notification which in turn will cause an event to be dispatched that
+ // runs all the rest of our shutdown sequence which we do not want to be
+ // processed on top of the running event.
+ // Thus we never do any NS_ProcessPendingEvents for it.
+ bool mayProcessPending = (aPhase > ShutdownPhase::AppShutdownConfirmed);
+
+ // Give runnables dispatched between two calls to AdvanceShutdownPhase
+ // a chance to run before actually advancing the phase. As we excluded
+ // AppShutdownConfirmed above we can be sure that the processing is
+ // covered by the terminator's timer of the previous phase during normal
+ // shutdown (except out-of-order calls from some test).
+ // Note that this affects only main thread runnables, such that the correct
+ // way of ensuring shutdown processing remains to have an async shutdown
+ // blocker.
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+
+ // From now on any IsInOrBeyond checks will find the new phase set.
+ sCurrentShutdownPhase = aPhase;
+
+#ifndef ANDROID
+ if (sTerminator) {
+ sTerminator->AdvancePhase(aPhase);
+ }
+#endif
+
+ AppShutdown::MaybeFastShutdown(aPhase);
+
+ // This will null out the gathered pointers for this phase synchronously.
+ // Note that we keep the old order here to avoid breakage, so be aware that
+ // the notifications fired below will find these already cleared in case
+ // you expected the opposite.
+ mozilla::KillClearOnShutdown(aPhase);
+
+ // Empty our MT event queue to process any side effects thereof.
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+
+ if (doNotify) {
+ const char* aTopic = AppShutdown::GetObserverKey(aPhase);
+ if (aTopic) {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+#ifdef DEBUG
+ sNotifyingShutdownObservers = true;
+ auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; });
+#endif
+ obsService->NotifyObservers(aNotificationSubject, aTopic,
+ aNotificationData);
+ // Empty our MT event queue again after the notification has finished
+ if (mayProcessPending && thread) {
+ NS_ProcessPendingEvents(thread);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * XXX: Before tackling bug 1697745 we need the
+ * possibility to advance the phase without notification
+ * in the content process.
+ */
+void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) {
+ AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr);
+}
+
+void AppShutdown::AdvanceShutdownPhase(
+ ShutdownPhase aPhase, const char16_t* aNotificationData,
+ const nsCOMPtr<nsISupports>& aNotificationSubject) {
+ AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData,
+ aNotificationSubject);
+}
+
+ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) {
+ for (size_t i = 0; i < ArrayLength(sPhaseObserverKeys); ++i) {
+ if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) {
+ return static_cast<ShutdownPhase>(i);
+ }
+ }
+ return ShutdownPhase::NotInShutdown;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/AppShutdown.h b/xpcom/base/AppShutdown.h
new file mode 100644
index 0000000000..6056338900
--- /dev/null
+++ b/xpcom/base/AppShutdown.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AppShutdown_h
+#define AppShutdown_h
+
+#include <type_traits>
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "ShutdownPhase.h"
+
+namespace mozilla {
+
+enum class AppShutdownMode {
+ Normal,
+ Restart,
+};
+
+enum class AppShutdownReason {
+ // No reason.
+ Unknown,
+ // Normal application shutdown.
+ AppClose,
+ // The application wants to restart.
+ AppRestart,
+ // The OS is force closing us.
+ OSForceClose,
+ // The user is logging off from the OS session, the system may stay alive.
+ OSSessionEnd,
+ // The system is shutting down (and maybe restarting).
+ OSShutdown,
+ // We unexpectedly received MOZ_WM_APP_QUIT, see bug 1827807.
+ WinUnexpectedMozQuit,
+};
+
+class AppShutdown {
+ public:
+ static ShutdownPhase GetCurrentShutdownPhase();
+ static bool IsInOrBeyond(ShutdownPhase aPhase);
+
+ /**
+ * Returns the current exit code that the process will be terminated with.
+ */
+ static int GetExitCode();
+
+ /**
+ * Save environment variables that we might need if the app initiates a
+ * restart later in its lifecycle.
+ */
+ static void SaveEnvVarsForPotentialRestart();
+
+ /**
+ * Init the shutdown with the requested shutdown mode, exit code and optional
+ * a reason (if missing it will be derived from aMode).
+ */
+ static void Init(AppShutdownMode aMode, int aExitCode,
+ AppShutdownReason aReason);
+
+ /**
+ * Confirm that we are in fact going to be shutting down.
+ */
+ static void OnShutdownConfirmed();
+
+ /**
+ * If we've attempted to initiate a restart, this call will set up the
+ * necessary environment variables and launch the new process.
+ */
+ static void MaybeDoRestart();
+
+ /**
+ * The _exit() call is not a safe way to terminate your own process on
+ * Windows, because _exit runs DLL detach callbacks which run static
+ * destructors for xul.dll.
+ *
+ * This method terminates the current process without those issues.
+ *
+ * Optionally a custom exit code can be supplied.
+ */
+ static void DoImmediateExit(int aExitCode = 0);
+
+ /**
+ * True if the application is currently attempting to shut down in order to
+ * restart.
+ */
+ static bool IsRestarting();
+
+ /**
+ * Wrapper for shutdown notifications that informs the terminator before
+ * we notify other observers. Calls MaybeFastShutdown.
+ */
+ static void AdvanceShutdownPhase(
+ ShutdownPhase aPhase, const char16_t* aNotificationData = nullptr,
+ const nsCOMPtr<nsISupports>& aNotificationSubject =
+ nsCOMPtr<nsISupports>(nullptr));
+
+ /**
+ * XXX: Before tackling bug 1697745 we need the
+ * possibility to advance the phase without notification
+ * in the content process.
+ */
+ static void AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase);
+
+ /**
+ * Map shutdown phase to observer key
+ */
+ static const char* GetObserverKey(ShutdownPhase aPhase);
+
+ /**
+ * Map shutdown phase to readable name
+ */
+ static const char* GetShutdownPhaseName(ShutdownPhase aPhase);
+
+ /**
+ * Map observer topic key to shutdown phase
+ */
+ static ShutdownPhase GetShutdownPhaseFromTopic(const char* aTopic);
+
+#ifdef DEBUG
+ /**
+ * Check, if we are allowed to send a shutdown notification.
+ * Shutdown specific topics are only allowed during calls to
+ * AdvanceShutdownPhase itself.
+ */
+ static bool IsNoOrLegalShutdownTopic(const char* aTopic);
+#endif
+
+ private:
+ /**
+ * Set the shutdown reason annotation.
+ */
+ static void AnnotateShutdownReason(AppShutdownReason aReason);
+
+ /**
+ * This will perform a fast shutdown via _exit(0) or similar if the user's
+ * prefs are configured to do so at this phase.
+ */
+ static void MaybeFastShutdown(ShutdownPhase aPhase);
+
+ /**
+ * Internal helper function, uses MaybeFastShutdown.
+ */
+ static void AdvanceShutdownPhaseInternal(
+ ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData,
+ const nsCOMPtr<nsISupports>& aNotificationSubject);
+};
+
+} // namespace mozilla
+
+#endif // AppShutdown_h
diff --git a/xpcom/base/AutoRestore.h b/xpcom/base/AutoRestore.h
new file mode 100644
index 0000000000..fc585da14b
--- /dev/null
+++ b/xpcom/base/AutoRestore.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* functions for restoring saved values at the end of a C++ scope */
+
+#ifndef mozilla_AutoRestore_h_
+#define mozilla_AutoRestore_h_
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+
+namespace mozilla {
+
+/**
+ * Save the current value of a variable and restore it when the object
+ * goes out of scope. For example:
+ * {
+ * AutoRestore<bool> savePainting(mIsPainting);
+ * mIsPainting = true;
+ *
+ * // ... your code here ...
+ *
+ * // mIsPainting is reset to its old value at the end of this block
+ * }
+ */
+template <class T>
+class MOZ_RAII AutoRestore {
+ private:
+ T& mLocation;
+ T mValue;
+
+ public:
+ explicit AutoRestore(T& aValue) : mLocation(aValue), mValue(aValue) {}
+ ~AutoRestore() { mLocation = mValue; }
+ T SavedValue() const { return mValue; }
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_AutoRestore_h_) */
diff --git a/xpcom/base/AvailableMemoryTracker.cpp b/xpcom/base/AvailableMemoryTracker.cpp
new file mode 100644
index 0000000000..a14c43bcbc
--- /dev/null
+++ b/xpcom/base/AvailableMemoryTracker.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AvailableMemoryTracker.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include "nsIMemoryReporter.h"
+#endif
+
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIRunnable.h"
+#include "nsISupports.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Services.h"
+
+#if defined(MOZ_MEMORY)
+# include "mozmemory.h"
+#endif // MOZ_MEMORY
+
+using namespace mozilla;
+
+Atomic<uint32_t, MemoryOrdering::Relaxed> sNumLowPhysicalMemEvents;
+
+namespace {
+
+#if defined(XP_WIN)
+
+# if defined(__MINGW32__)
+// Definitions for heap optimization that require the Windows SDK to target the
+// Windows 8.1 Update
+static const HEAP_INFORMATION_CLASS HeapOptimizeResources =
+ static_cast<HEAP_INFORMATION_CLASS>(3);
+
+static const DWORD HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION = 1;
+
+typedef struct _HEAP_OPTIMIZE_RESOURCES_INFORMATION {
+ DWORD Version;
+ DWORD Flags;
+} HEAP_OPTIMIZE_RESOURCES_INFORMATION, *PHEAP_OPTIMIZE_RESOURCES_INFORMATION;
+# endif
+
+static int64_t LowMemoryEventsPhysicalDistinguishedAmount() {
+ return sNumLowPhysicalMemEvents;
+}
+
+class LowEventsReporter final : public nsIMemoryReporter {
+ ~LowEventsReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE,
+ LowMemoryEventsPhysicalDistinguishedAmount(),
+"Number of low-physical-memory events fired since startup. We fire such an "
+"event when a windows low memory resource notification is signaled. The "
+"machine will start to page if it runs out of physical memory. This may "
+"cause it to run slowly, but it shouldn't cause it to crash.");
+ // clang-format on
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter)
+
+#endif // defined(XP_WIN)
+
+/**
+ * This runnable is executed in response to a memory-pressure event; we spin
+ * the event-loop when receiving the memory-pressure event in the hope that
+ * other observers will synchronously free some memory that we'll be able to
+ * purge here.
+ */
+class nsJemallocFreeDirtyPagesRunnable final : public Runnable {
+ ~nsJemallocFreeDirtyPagesRunnable() = default;
+
+#if defined(XP_WIN)
+ void OptimizeSystemHeap();
+#endif
+
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ nsJemallocFreeDirtyPagesRunnable()
+ : Runnable("nsJemallocFreeDirtyPagesRunnable") {}
+};
+
+NS_IMETHODIMP
+nsJemallocFreeDirtyPagesRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if defined(MOZ_MEMORY)
+ jemalloc_free_dirty_pages();
+#endif
+
+#if defined(XP_WIN)
+ OptimizeSystemHeap();
+#endif
+
+ return NS_OK;
+}
+
+#if defined(XP_WIN)
+void nsJemallocFreeDirtyPagesRunnable::OptimizeSystemHeap() {
+ HEAP_OPTIMIZE_RESOURCES_INFORMATION heapOptInfo = {
+ HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION};
+
+ ::HeapSetInformation(nullptr, HeapOptimizeResources, &heapOptInfo,
+ sizeof(heapOptInfo));
+}
+#endif // defined(XP_WIN)
+
+/**
+ * The memory pressure watcher is used for listening to memory-pressure events
+ * and reacting upon them. We use one instance per process currently only for
+ * cleaning up dirty unused pages held by jemalloc.
+ */
+class nsMemoryPressureWatcher final : public nsIObserver {
+ ~nsMemoryPressureWatcher() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void Init();
+};
+
+NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver)
+
+/**
+ * Initialize and subscribe to the memory-pressure events. We subscribe to the
+ * observer service in this method and not in the constructor because we need
+ * to hold a strong reference to 'this' before calling the observer service.
+ */
+void nsMemoryPressureWatcher::Init() {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+ if (os) {
+ os->AddObserver(this, "memory-pressure", /* ownsWeak */ false);
+ }
+}
+
+/**
+ * Reacts to all types of memory-pressure events, launches a runnable to
+ * free dirty pages held by jemalloc.
+ */
+NS_IMETHODIMP
+nsMemoryPressureWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"), "Unknown topic");
+
+ nsCOMPtr<nsIRunnable> runnable = new nsJemallocFreeDirtyPagesRunnable();
+
+ NS_DispatchToMainThread(runnable);
+
+ return NS_OK;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace AvailableMemoryTracker {
+
+void Init() {
+ // The watchers are held alive by the observer service.
+ RefPtr<nsMemoryPressureWatcher> watcher = new nsMemoryPressureWatcher();
+ watcher->Init();
+
+#if defined(XP_WIN)
+ RegisterLowMemoryEventsPhysicalDistinguishedAmount(
+ LowMemoryEventsPhysicalDistinguishedAmount);
+#endif // defined(XP_WIN)
+}
+
+} // namespace AvailableMemoryTracker
+} // namespace mozilla
diff --git a/xpcom/base/AvailableMemoryTracker.h b/xpcom/base/AvailableMemoryTracker.h
new file mode 100644
index 0000000000..3d2a048cb3
--- /dev/null
+++ b/xpcom/base/AvailableMemoryTracker.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AvailableMemoryTracker_h
+#define mozilla_AvailableMemoryTracker_h
+
+namespace mozilla {
+namespace AvailableMemoryTracker {
+
+// The AvailableMemoryTracker launches a memory pressure watcher on all
+// platforms to react to low-memory situations and on Windows it implements
+// the full functionality used to monitor how much memory is available.
+//
+// Init() requires the observer service to be already available so cannot be
+// called too early during initialization.
+
+void Init();
+
+} // namespace AvailableMemoryTracker
+} // namespace mozilla
+
+#endif // ifndef mozilla_AvailableMemoryTracker_h
diff --git a/xpcom/base/AvailableMemoryWatcher.cpp b/xpcom/base/AvailableMemoryWatcher.cpp
new file mode 100644
index 0000000000..a2de368b01
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcher.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AvailableMemoryWatcher.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "nsExceptionHandler.h"
+#include "nsMemoryPressure.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+
+// Use this class as the initial value of
+// nsAvailableMemoryWatcherBase::mCallback until RegisterCallback() is called
+// so that nsAvailableMemoryWatcherBase does not have to check if its callback
+// object is valid or not.
+class NullTabUnloader final : public nsITabUnloader {
+ ~NullTabUnloader() = default;
+
+ public:
+ NullTabUnloader() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITABUNLOADER
+};
+
+NS_IMPL_ISUPPORTS(NullTabUnloader, nsITabUnloader)
+
+NS_IMETHODIMP NullTabUnloader::UnloadTabAsync() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+StaticRefPtr<nsAvailableMemoryWatcherBase>
+ nsAvailableMemoryWatcherBase::sSingleton;
+
+/*static*/
+already_AddRefed<nsAvailableMemoryWatcherBase>
+nsAvailableMemoryWatcherBase::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = CreateAvailableMemoryWatcher();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return do_AddRef(sSingleton);
+}
+
+NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcherBase, nsIAvailableMemoryWatcherBase);
+
+nsAvailableMemoryWatcherBase::nsAvailableMemoryWatcherBase()
+ : mMutex("nsAvailableMemoryWatcher mutex"),
+ mNumOfTabUnloading(0),
+ mNumOfMemoryPressure(0),
+ mTabUnloader(new NullTabUnloader),
+ mInteracting(false) {
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Watching memory only in the main process.");
+}
+
+const char* const nsAvailableMemoryWatcherBase::kObserverTopics[] = {
+ // Use this shutdown phase to make sure the instance is destroyed in GTest
+ "xpcom-shutdown",
+ "user-interaction-active",
+ "user-interaction-inactive",
+};
+
+nsresult nsAvailableMemoryWatcherBase::Init() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "nsAvailableMemoryWatcherBase needs to be initialized "
+ "in the main thread.");
+
+ if (mObserverSvc) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ mObserverSvc = services::GetObserverService();
+ MOZ_ASSERT(mObserverSvc);
+
+ for (auto topic : kObserverTopics) {
+ nsresult rv = mObserverSvc->AddObserver(this, topic,
+ /* ownsWeak */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+void nsAvailableMemoryWatcherBase::Shutdown() {
+ for (auto topic : kObserverTopics) {
+ mObserverSvc->RemoveObserver(this, topic);
+ }
+}
+
+NS_IMETHODIMP
+nsAvailableMemoryWatcherBase::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+ Shutdown();
+ } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
+ mInteracting = false;
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::SetInactiveStateStart();
+#endif
+ } else if (strcmp(aTopic, "user-interaction-active") == 0) {
+ mInteracting = true;
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::ClearInactiveStateStart();
+#endif
+ }
+ return NS_OK;
+}
+
+nsresult nsAvailableMemoryWatcherBase::RegisterTabUnloader(
+ nsITabUnloader* aTabUnloader) {
+ mTabUnloader = aTabUnloader;
+ return NS_OK;
+}
+
+// This method is called as a part of UnloadTabAsync(). Like Notify(), if we
+// call this method synchronously without releasing the lock first we can lead
+// to deadlock.
+nsresult nsAvailableMemoryWatcherBase::OnUnloadAttemptCompleted(
+ nsresult aResult) {
+ MutexAutoLock lock(mMutex);
+ switch (aResult) {
+ // A tab was unloaded successfully.
+ case NS_OK:
+ ++mNumOfTabUnloading;
+ break;
+
+ // There was no unloadable tab.
+ case NS_ERROR_NOT_AVAILABLE:
+ ++mNumOfMemoryPressure;
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ break;
+
+ // There was a pending task to unload a tab.
+ case NS_ERROR_ABORT:
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected aResult");
+ break;
+ }
+ return NS_OK;
+}
+
+void nsAvailableMemoryWatcherBase::UpdateLowMemoryTimeStamp() {
+ if (mLowMemoryStart.IsNull()) {
+ mLowMemoryStart = TimeStamp::NowLoRes();
+ }
+}
+
+void nsAvailableMemoryWatcherBase::RecordTelemetryEventOnHighMemory(
+ const MutexAutoLock&) {
+ Telemetry::SetEventRecordingEnabled("memory_watcher"_ns, true);
+ Telemetry::RecordEvent(
+ Telemetry::EventID::Memory_watcher_OnHighMemory_Stats,
+ Some(nsPrintfCString(
+ "%u,%u,%f", mNumOfTabUnloading, mNumOfMemoryPressure,
+ (TimeStamp::NowLoRes() - mLowMemoryStart).ToSeconds())),
+ Nothing());
+ mNumOfTabUnloading = mNumOfMemoryPressure = 0;
+ mLowMemoryStart = TimeStamp();
+}
+
+// Define the fallback method for a platform for which a platform-specific
+// CreateAvailableMemoryWatcher() is not defined.
+#if defined(ANDROID) || \
+ !defined(XP_WIN) && !defined(XP_DARWIN) && !defined(XP_LINUX)
+already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
+ RefPtr instance(new nsAvailableMemoryWatcherBase);
+ return do_AddRef(instance);
+}
+#endif
+
+} // namespace mozilla
diff --git a/xpcom/base/AvailableMemoryWatcher.h b/xpcom/base/AvailableMemoryWatcher.h
new file mode 100644
index 0000000000..a5acfbb7c5
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcher.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AvailableMemoryWatcher_h
+#define mozilla_AvailableMemoryWatcher_h
+
+#include "mozilla/ipc/CrashReporterHost.h"
+#include "mozilla/UniquePtr.h"
+#include "MemoryPressureLevelMac.h"
+#include "nsCOMPtr.h"
+#include "nsIAvailableMemoryWatcherBase.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+
+namespace mozilla {
+
+// This class implements a platform-independent part to watch the system's
+// memory situation and invoke the registered callbacks when we detect
+// a low-memory situation or a high-memory situation.
+// The actual logic to monitor the memory status is implemented in a subclass
+// of nsAvailableMemoryWatcherBase per platform.
+class nsAvailableMemoryWatcherBase : public nsIAvailableMemoryWatcherBase,
+ public nsIObserver {
+ static StaticRefPtr<nsAvailableMemoryWatcherBase> sSingleton;
+ static const char* const kObserverTopics[];
+
+ TimeStamp mLowMemoryStart;
+
+ protected:
+ // On Windows the publicly available methods (::Observe() and ::Notify()) are
+ // called on the main thread while the ::LowMemoryCallback() method is called
+ // by an external thread. All functions called from those must acquire a lock
+ // on this mutex before accessing the object's fields to prevent races.
+ //
+ // On Linux we might tell polling to start/stop from our polling thread
+ // or from the main thread during ::Observe().
+ Mutex mMutex;
+
+ uint32_t mNumOfTabUnloading MOZ_GUARDED_BY(mMutex);
+ uint32_t mNumOfMemoryPressure MOZ_GUARDED_BY(mMutex);
+
+ nsCOMPtr<nsITabUnloader> mTabUnloader;
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ // Do not change this value off the main thread.
+ bool mInteracting;
+
+ virtual ~nsAvailableMemoryWatcherBase() = default;
+ virtual nsresult Init();
+ void Shutdown();
+ void UpdateLowMemoryTimeStamp();
+ void RecordTelemetryEventOnHighMemory(const MutexAutoLock&)
+ MOZ_REQUIRES(mMutex);
+
+ public:
+ static already_AddRefed<nsAvailableMemoryWatcherBase> GetSingleton();
+
+ nsAvailableMemoryWatcherBase();
+
+#if defined(XP_DARWIN)
+ virtual void OnMemoryPressureChanged(MacMemoryPressureLevel aNewLevel){};
+ virtual void AddChildAnnotations(
+ const UniquePtr<ipc::CrashReporterHost>& aCrashReporter){};
+#endif
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIAVAILABLEMEMORYWATCHERBASE
+ NS_DECL_NSIOBSERVER
+};
+
+// Method to create a platform-specific object
+already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher();
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_AvailableMemoryWatcher_h
diff --git a/xpcom/base/AvailableMemoryWatcherLinux.cpp b/xpcom/base/AvailableMemoryWatcherLinux.cpp
new file mode 100644
index 0000000000..04927b7d46
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcherLinux.cpp
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "AvailableMemoryWatcher.h"
+#include "AvailableMemoryWatcherUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/Unused.h"
+#include "nsAppRunner.h"
+#include "nsIObserverService.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsIThread.h"
+#include "nsMemoryPressure.h"
+
+namespace mozilla {
+
+// Linux has no native low memory detection. This class creates a timer that
+// polls for low memory and sends a low memory notification if it notices a
+// memory pressure event.
+class nsAvailableMemoryWatcher final : public nsITimerCallback,
+ public nsINamed,
+ public nsAvailableMemoryWatcherBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ nsresult Init() override;
+ nsAvailableMemoryWatcher();
+
+ void HandleLowMemory();
+ void MaybeHandleHighMemory();
+
+ private:
+ ~nsAvailableMemoryWatcher() = default;
+ void StartPolling(const MutexAutoLock&);
+ void StopPolling(const MutexAutoLock&);
+ void ShutDown();
+ void UpdateCrashAnnotation(const MutexAutoLock&);
+ static bool IsMemoryLow();
+
+ nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIThread> mThread MOZ_GUARDED_BY(mMutex);
+
+ bool mPolling MOZ_GUARDED_BY(mMutex);
+ bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex);
+
+ // Polling interval to check for low memory. In high memory scenarios,
+ // default to 5000 ms between each check.
+ static const uint32_t kHighMemoryPollingIntervalMS = 5000;
+
+ // Polling interval to check for low memory. Default to 1000 ms between each
+ // check. Use this interval when memory is low,
+ static const uint32_t kLowMemoryPollingIntervalMS = 1000;
+};
+
+// A modern version of linux should keep memory information in the
+// /proc/meminfo path.
+static const char* kMeminfoPath = "/proc/meminfo";
+
+nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
+ : mPolling(false), mUnderMemoryPressure(false) {}
+
+nsresult nsAvailableMemoryWatcher::Init() {
+ nsresult rv = nsAvailableMemoryWatcherBase::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ MutexAutoLock lock(mMutex);
+ mTimer = NS_NewTimer();
+ nsCOMPtr<nsIThread> thread;
+ // We have to make our own thread here instead of using the background pool,
+ // because some low memory scenarios can cause the background pool to fill.
+ rv = NS_NewNamedThread("MemoryPoller", getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Couldn't make a thread for nsAvailableMemoryWatcher.");
+ // In this scenario we can't poll for low memory, since we can't dispatch
+ // to our memory watcher thread.
+ return rv;
+ }
+ mThread = thread;
+
+ // Set the crash annotation to its initial state.
+ UpdateCrashAnnotation(lock);
+
+ StartPolling(lock);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
+ RefPtr watcher(new nsAvailableMemoryWatcher);
+
+ if (NS_FAILED(watcher->Init())) {
+ return do_AddRef(new nsAvailableMemoryWatcherBase);
+ }
+
+ return watcher.forget();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
+ nsAvailableMemoryWatcherBase, nsITimerCallback,
+ nsIObserver, nsINamed);
+
+void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&)
+ MOZ_REQUIRES(mMutex) {
+ if (mPolling && mTimer) {
+ // stop dispatching memory checks to the thread.
+ mTimer->Cancel();
+ mPolling = false;
+ }
+}
+
+// Check /proc/meminfo for low memory. Largely C method for reading
+// /proc/meminfo.
+/* static */
+bool nsAvailableMemoryWatcher::IsMemoryLow() {
+ MemoryInfo memInfo{0, 0};
+ nsresult rv = ReadMemoryFile(kMeminfoPath, memInfo);
+
+ if (NS_FAILED(rv) || (memInfo.memAvailable == 0) || (memInfo.memTotal == 0)) {
+ // If memAvailable cannot be found, then we are using an older system.
+ // We can't accurately poll on this.
+ // If memTotal is zero we can't calculate how much memory we're using.
+ return false;
+ }
+
+ unsigned long memoryAsPercentage =
+ (memInfo.memAvailable * 100) / memInfo.memTotal;
+
+ return memoryAsPercentage <=
+ StaticPrefs::browser_low_commit_space_threshold_percent() ||
+ memInfo.memAvailable <
+ StaticPrefs::browser_low_commit_space_threshold_mb() * 1024;
+}
+
+void nsAvailableMemoryWatcher::ShutDown() {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ thread = mThread.forget();
+ }
+ // thread->Shutdown() spins a nested event loop while waiting for the thread
+ // to end. But the thread might execute some previously dispatched event that
+ // wants to lock our mutex, too, before arriving at the shutdown event.
+ if (thread) {
+ thread->Shutdown();
+ }
+}
+
+// We will use this to poll for low memory.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
+ MutexAutoLock lock(mMutex);
+ if (!mThread) {
+ // If we've made it this far and there's no |mThread|,
+ // we might have failed to dispatch it for some reason.
+ MOZ_ASSERT(mThread);
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = mThread->Dispatch(
+ NS_NewRunnableFunction("MemoryPoller", [self = RefPtr{this}]() {
+ if (self->IsMemoryLow()) {
+ self->HandleLowMemory();
+ } else {
+ self->MaybeHandleHighMemory();
+ }
+ }));
+
+ if NS_FAILED (rv) {
+ NS_WARNING("Cannot dispatch memory polling event.");
+ }
+ return NS_OK;
+}
+
+void nsAvailableMemoryWatcher::HandleLowMemory() {
+ MutexAutoLock lock(mMutex);
+ if (!mTimer) {
+ // We have been shut down from outside while in flight.
+ return;
+ }
+ if (!mUnderMemoryPressure) {
+ mUnderMemoryPressure = true;
+ UpdateCrashAnnotation(lock);
+ // Poll more frequently under memory pressure.
+ StartPolling(lock);
+ }
+ UpdateLowMemoryTimeStamp();
+ // We handle low memory offthread, but we want to unload
+ // tabs only from the main thread, so we will dispatch this
+ // back to the main thread.
+ // Since we are doing this async, we don't need to unlock the mutex first;
+ // the AutoLock will unlock the mutex when we finish the dispatch.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAvailableMemoryWatcher::OnLowMemory",
+ [self = RefPtr{this}]() { self->mTabUnloader->UnloadTabAsync(); }));
+}
+
+void nsAvailableMemoryWatcher::UpdateCrashAnnotation(const MutexAutoLock&)
+ MOZ_REQUIRES(mMutex) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::LinuxUnderMemoryPressure,
+ mUnderMemoryPressure);
+}
+
+// If memory is not low, we may need to dispatch an
+// event for it if we have been under memory pressure.
+// We can also adjust our polling interval.
+void nsAvailableMemoryWatcher::MaybeHandleHighMemory() {
+ MutexAutoLock lock(mMutex);
+ if (!mTimer) {
+ // We have been shut down from outside while in flight.
+ return;
+ }
+ if (mUnderMemoryPressure) {
+ RecordTelemetryEventOnHighMemory(lock);
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure);
+ mUnderMemoryPressure = false;
+ UpdateCrashAnnotation(lock);
+ }
+ StartPolling(lock);
+}
+
+// When we change the polling interval, we will need to restart the timer
+// on the new interval.
+void nsAvailableMemoryWatcher::StartPolling(const MutexAutoLock& aLock)
+ MOZ_REQUIRES(mMutex) {
+ uint32_t pollingInterval = mUnderMemoryPressure
+ ? kLowMemoryPollingIntervalMS
+ : kHighMemoryPollingIntervalMS;
+ if (!mPolling) {
+ // Restart the timer with the new interval if it has stopped.
+ // For testing, use a small polling interval.
+ if (NS_SUCCEEDED(
+ mTimer->InitWithCallback(this, gIsGtest ? 10 : pollingInterval,
+ nsITimer::TYPE_REPEATING_SLACK))) {
+ mPolling = true;
+ }
+ } else {
+ mTimer->SetDelay(gIsGtest ? 10 : pollingInterval);
+ }
+}
+
+// Observe events for shutting down and starting/stopping the timer.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+ ShutDown();
+ } else {
+ MutexAutoLock lock(mMutex);
+ if (mTimer) {
+ if (strcmp(aTopic, "user-interaction-active") == 0) {
+ StartPolling(lock);
+ } else if (strcmp(aTopic, "user-interaction-inactive") == 0) {
+ StopPolling(lock);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAvailableMemoryWatcher::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAvailableMemoryWatcher");
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/AvailableMemoryWatcherMac.cpp b/xpcom/base/AvailableMemoryWatcherMac.cpp
new file mode 100644
index 0000000000..d6f2d16b30
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcherMac.cpp
@@ -0,0 +1,634 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "AvailableMemoryWatcher.h"
+#include "Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsICrashReporter.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+#include "nsPrintfCString.h"
+
+#define MP_LOG(...) MOZ_LOG(gMPLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gMPLog("MemoryPressure");
+
+namespace mozilla {
+
+/*
+ * The Mac AvailableMemoryWatcher works as follows. When the OS memory pressure
+ * level changes on macOS, nsAvailableMemoryWatcher::OnMemoryPressureChanged()
+ * is called with the new memory pressure level. The level is represented in
+ * Gecko by a MacMemoryPressureLevel instance and represents the states of
+ * normal, warning, or critical which correspond to the native levels. When the
+ * browser launches, the initial level is determined using a sysctl. Which
+ * actions are taken in the browser in response to memory pressure, and the
+ * level (warning or critical) which trigger the reponse is configurable with
+ * prefs to make it easier to perform experiments to study how the response
+ * affects the user experience.
+ *
+ * By default, the browser responds by attempting to reduce memory use when the
+ * OS transitions to the critical level and while it stays in the critical
+ * level. i.e., "critical" OS memory pressure is the default threshold for the
+ * low memory response. Setting pref "browser.lowMemoryResponseOnWarn" to true
+ * changes the memory response to occur at the "warning" level which is less
+ * severe than "critical". When entering the critical level, we begin polling
+ * the memory pressure level every 'n' milliseconds (specified via the pref
+ * "browser.lowMemoryPollingIntervalMS"). Each time the poller wakes up and
+ * finds the OS still under memory pressure, the low memory response is
+ * executed.
+ *
+ * By default, the memory pressure response is, in order, to
+ * 1) call nsITabUnloader::UnloadTabAsync(),
+ * 2) if no tabs could be unloaded, issue a Gecko
+ * MemoryPressureState::LowMemory notification.
+ * The response can be changed via the pref "browser.lowMemoryResponseMask" to
+ * limit the actions to only tab unloading or Gecko memory pressure
+ * notifications.
+ *
+ * Polling occurs on the main thread because, at each polling interval, we
+ * call into the tab unloader which requires being on the main thread.
+ * Polling only occurs while under OS memory pressure at the critical (by
+ * default) level.
+ */
+class nsAvailableMemoryWatcher final : public nsITimerCallback,
+ public nsINamed,
+ public nsAvailableMemoryWatcherBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsAvailableMemoryWatcher();
+ nsresult Init() override;
+
+ void OnMemoryPressureChanged(MacMemoryPressureLevel aLevel) override;
+ void AddChildAnnotations(
+ const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) override;
+
+ private:
+ ~nsAvailableMemoryWatcher(){};
+
+ void OnMemoryPressureChangedInternal(MacMemoryPressureLevel aNewLevel,
+ bool aIsInitialLevel);
+
+ // Override OnUnloadAttemptCompleted() so that we can control whether
+ // or not a Gecko memory-pressure event is sent after a tab unload attempt.
+ // This method is called externally by the tab unloader after a tab unload
+ // attempt. It is used internally when tab unloading is disabled in
+ // mResponseMask.
+ nsresult OnUnloadAttemptCompleted(nsresult aResult) override;
+
+ void OnShutdown();
+ void OnPrefChange();
+
+ void InitParentAnnotations();
+ void UpdateParentAnnotations();
+
+ void AddParentAnnotation(CrashReporter::Annotation aAnnotation,
+ nsAutoCString aString) {
+ CrashReporter::AnnotateCrashReport(aAnnotation, aString);
+ }
+ void AddParentAnnotation(CrashReporter::Annotation aAnnotation,
+ uint32_t aData) {
+ CrashReporter::AnnotateCrashReport(aAnnotation, aData);
+ }
+
+ void LowMemoryResponse();
+ void StartPolling();
+ void StopPolling();
+ void RestartPolling();
+ inline bool IsPolling() { return mTimer; }
+
+ void ReadSysctls();
+
+ // This enum represents the allowed values for the pref that controls
+ // the low memory response - "browser.lowMemoryResponseMask". Specifically,
+ // whether or not we unload tabs and/or issue the Gecko "memory-pressure"
+ // internal notification. For tab unloading, the pref
+ // "browser.tabs.unloadOnLowMemory" must also be set.
+ enum ResponseMask {
+ eNone = 0x0,
+ eTabUnload = 0x1,
+ eInternalMemoryPressure = 0x2,
+ eAll = 0x3,
+ };
+ static constexpr char kResponseMask[] = "browser.lowMemoryResponseMask";
+ static const uint32_t kResponseMaskDefault;
+ static const uint32_t kResponseMaskMax;
+
+ // Pref for controlling how often we wake up during an OS memory pressure
+ // time period. At each wakeup, we unload tabs and issue the Gecko
+ // "memory-pressure" internal notification. When not under OS memory pressure,
+ // polling is disabled.
+ static constexpr char kPollingIntervalMS[] =
+ "browser.lowMemoryPollingIntervalMS";
+ static const uint32_t kPollingIntervalMaxMS;
+ static const uint32_t kPollingIntervalMinMS;
+ static const uint32_t kPollingIntervalDefaultMS;
+
+ static constexpr char kResponseOnWarn[] = "browser.lowMemoryResponseOnWarn";
+ static const bool kResponseLevelOnWarnDefault = false;
+
+ // Init has been called.
+ bool mInitialized;
+
+ // The memory pressure reported to the application by macOS.
+ MacMemoryPressureLevel mLevel;
+
+ // The OS memory pressure level that triggers the response.
+ MacMemoryPressureLevel mResponseLevel;
+
+ // The value of the kern.memorystatus_vm_pressure_level sysctl. The OS
+ // notifies the application when the memory pressure level changes,
+ // but the sysctl value can be read at any time. Unofficially, the sysctl
+ // value corresponds to the OS memory pressure level with 4=>critical,
+ // 2=>warning, and 1=>normal (values from kernel event.h file).
+ uint32_t mLevelSysctl;
+ static const int kSysctlLevelNormal = 0x1;
+ static const int kSysctlLevelWarning = 0x2;
+ static const int kSysctlLevelCritical = 0x4;
+
+ // The value of the kern.memorystatus_level sysctl. Unofficially,
+ // this is the percentage of available memory. (Also readable
+ // via the undocumented memorystatus_get_level syscall.)
+ int mAvailMemSysctl;
+
+ // The string representation of `mLevel`. i.e., normal, warning, or critical.
+ // Set to "unset" until a memory pressure change is reported to the process
+ // by the OS.
+ nsAutoCString mLevelStr;
+
+ // Timestamps for memory pressure level changes. Specifically, the Unix
+ // time in string form. Saved as Unix time to allow comparisons with
+ // the crash time.
+ nsAutoCString mNormalTimeStr;
+ nsAutoCString mWarningTimeStr;
+ nsAutoCString mCriticalTimeStr;
+
+ nsCOMPtr<nsITimer> mTimer; // non-null indicates the timer is active
+
+ // Saved pref values.
+ uint32_t mPollingInterval;
+ uint32_t mResponseMask;
+};
+
+const uint32_t nsAvailableMemoryWatcher::kResponseMaskDefault =
+ ResponseMask::eAll;
+const uint32_t nsAvailableMemoryWatcher::kResponseMaskMax = ResponseMask::eAll;
+
+// 10 seconds
+const uint32_t nsAvailableMemoryWatcher::kPollingIntervalDefaultMS = 10'000;
+// 10 minutes
+const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMaxMS = 600'000;
+// 100 milliseconds
+const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMinMS = 100;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
+ nsAvailableMemoryWatcherBase, nsIObserver,
+ nsITimerCallback, nsINamed)
+
+nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
+ : mInitialized(false),
+ mLevel(MacMemoryPressureLevel::Value::eUnset),
+ mResponseLevel(MacMemoryPressureLevel::Value::eCritical),
+ mLevelSysctl(0xFFFFFFFF),
+ mAvailMemSysctl(-1),
+ mLevelStr("Unset"),
+ mNormalTimeStr("Unset"),
+ mWarningTimeStr("Unset"),
+ mCriticalTimeStr("Unset"),
+ mPollingInterval(0),
+ mResponseMask(ResponseMask::eAll) {}
+
+nsresult nsAvailableMemoryWatcher::Init() {
+ nsresult rv = nsAvailableMemoryWatcherBase::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Users of nsAvailableMemoryWatcher should use
+ // nsAvailableMemoryWatcherBase::GetSingleton() and not call Init directly.
+ MOZ_ASSERT(!mInitialized);
+ if (mInitialized) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // Read polling frequency pref
+ mPollingInterval =
+ Preferences::GetUint(kPollingIntervalMS, kPollingIntervalDefaultMS);
+ mPollingInterval = std::clamp(mPollingInterval, kPollingIntervalMinMS,
+ kPollingIntervalMaxMS);
+
+ // Read response bitmask pref which (along with the main tab unloading
+ // preference) controls whether or not tab unloading and Gecko (internal)
+ // memory pressure notifications will be sent. The main tab unloading
+ // preference must also be enabled for tab unloading to occur.
+ mResponseMask = Preferences::GetUint(kResponseMask, kResponseMaskDefault);
+ if (mResponseMask > kResponseMaskMax) {
+ mResponseMask = kResponseMaskMax;
+ }
+
+ // Read response level pref
+ if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) {
+ mResponseLevel = MacMemoryPressureLevel::Value::eWarning;
+ } else {
+ mResponseLevel = MacMemoryPressureLevel::Value::eCritical;
+ }
+
+ ReadSysctls();
+ MP_LOG("Initial memory pressure sysctl: %d", mLevelSysctl);
+ MP_LOG("Initial available memory sysctl: %d", mAvailMemSysctl);
+
+ // Set the initial state of all annotations for parent crash reports.
+ // Content process crash reports are set when a crash occurs and
+ // AddChildAnnotations() is called.
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacMemoryPressure, mLevelStr);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacMemoryPressureCriticalTime,
+ mCriticalTimeStr);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl);
+
+ // To support running experiments, handle pref
+ // changes without requiring a browser restart.
+ rv = Preferences::AddStrongObserver(this, kResponseMask);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ nsPrintfCString("Failed to add %s observer", kResponseMask).get());
+ }
+ rv = Preferences::AddStrongObserver(this, kPollingIntervalMS);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ nsPrintfCString("Failed to add %s observer", kPollingIntervalMS).get());
+ }
+ rv = Preferences::AddStrongObserver(this, kResponseOnWarn);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ nsPrintfCString("Failed to add %s observer", kResponseOnWarn).get());
+ }
+
+ // Use the memory pressure sysctl to initialize our memory pressure state.
+ MacMemoryPressureLevel initialLevel;
+ switch (mLevelSysctl) {
+ case kSysctlLevelNormal:
+ initialLevel = MacMemoryPressureLevel::Value::eNormal;
+ break;
+ case kSysctlLevelWarning:
+ initialLevel = MacMemoryPressureLevel::Value::eWarning;
+ break;
+ case kSysctlLevelCritical:
+ initialLevel = MacMemoryPressureLevel::Value::eCritical;
+ break;
+ default:
+ initialLevel = MacMemoryPressureLevel::Value::eUnexpected;
+ }
+
+ OnMemoryPressureChangedInternal(initialLevel, /* aIsInitialLevel */ true);
+ mInitialized = true;
+ return NS_OK;
+}
+
+already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
+ // Users of nsAvailableMemoryWatcher should use
+ // nsAvailableMemoryWatcherBase::GetSingleton().
+ RefPtr watcher(new nsAvailableMemoryWatcher());
+ watcher->Init();
+ return watcher.forget();
+}
+
+// Update the memory pressure level, level change timestamps, and sysctl
+// level crash report annotations.
+void nsAvailableMemoryWatcher::UpdateParentAnnotations() {
+ // Generate a string representation of the current Unix time.
+ time_t timeChanged = time(NULL);
+ nsAutoCString timeChangedString;
+ timeChangedString =
+ nsPrintfCString("%" PRIu64, static_cast<uint64_t>(timeChanged));
+
+ nsAutoCString pressureLevelString;
+ Maybe<CrashReporter::Annotation> pressureLevelKey;
+
+ switch (mLevel.GetValue()) {
+ case MacMemoryPressureLevel::Value::eNormal:
+ mNormalTimeStr = timeChangedString;
+ pressureLevelString = "Normal";
+ pressureLevelKey.emplace(
+ CrashReporter::Annotation::MacMemoryPressureNormalTime);
+ break;
+ case MacMemoryPressureLevel::Value::eWarning:
+ mWarningTimeStr = timeChangedString;
+ pressureLevelString = "Warning";
+ pressureLevelKey.emplace(
+ CrashReporter::Annotation::MacMemoryPressureWarningTime);
+ break;
+ case MacMemoryPressureLevel::Value::eCritical:
+ mCriticalTimeStr = timeChangedString;
+ pressureLevelString = "Critical";
+ pressureLevelKey.emplace(
+ CrashReporter::Annotation::MacMemoryPressureCriticalTime);
+ break;
+ default:
+ pressureLevelString = "Unexpected";
+ break;
+ }
+
+ // Save the current memory pressure level.
+ AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressure,
+ pressureLevelString);
+
+ // Save the time we transitioned to the current memory pressure level.
+ if (pressureLevelKey.isSome()) {
+ AddParentAnnotation(pressureLevelKey.value(), timeChangedString);
+ }
+
+ AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressureSysctl,
+ mLevelSysctl);
+ AddParentAnnotation(CrashReporter::Annotation::MacAvailableMemorySysctl,
+ mAvailMemSysctl);
+}
+
+void nsAvailableMemoryWatcher::ReadSysctls() {
+ // Pressure level
+ uint32_t level;
+ size_t size = sizeof(level);
+ if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, NULL,
+ 0) == -1) {
+ MP_LOG("Failure reading memory pressure sysctl");
+ }
+ mLevelSysctl = level;
+
+ // Available memory percent
+ int availPercent;
+ size = sizeof(availPercent);
+ if (sysctlbyname("kern.memorystatus_level", &availPercent, &size, NULL, 0) ==
+ -1) {
+ MP_LOG("Failure reading available memory level");
+ }
+ mAvailMemSysctl = availPercent;
+}
+
+/* virtual */
+void nsAvailableMemoryWatcher::OnMemoryPressureChanged(
+ MacMemoryPressureLevel aNewLevel) {
+ MOZ_ASSERT(mInitialized);
+ OnMemoryPressureChangedInternal(aNewLevel, /* aIsInitialLevel */ false);
+}
+
+void nsAvailableMemoryWatcher::OnMemoryPressureChangedInternal(
+ MacMemoryPressureLevel aNewLevel, bool aIsInitialLevel) {
+ MOZ_ASSERT(mInitialized || aIsInitialLevel);
+ MP_LOG("MemoryPressureChange: existing level: %s, new level: %s",
+ mLevel.ToString(), aNewLevel.ToString());
+
+ // If 'aNewLevel' is not one of normal, warning, or critical, ASSERT
+ // here so we can debug this scenario. For non-debug builds, ignore
+ // the unexpected value which will be logged in crash reports.
+ MOZ_ASSERT(aNewLevel.IsNormal() || aNewLevel.IsWarningOrAbove());
+
+ if (mLevel == aNewLevel) {
+ return;
+ }
+
+ // Start the memory pressure response if the new level is high enough
+ // and the existing level was not.
+ if ((mLevel < mResponseLevel) && (aNewLevel >= mResponseLevel)) {
+ UpdateLowMemoryTimeStamp();
+ LowMemoryResponse();
+ if (mResponseMask) {
+ StartPolling();
+ }
+ }
+
+ // End the memory pressure reponse if the new level is not high enough.
+ if ((mLevel >= mResponseLevel) && (aNewLevel < mResponseLevel)) {
+ {
+ MutexAutoLock lock(mMutex);
+ RecordTelemetryEventOnHighMemory(lock);
+ }
+ StopPolling();
+ MP_LOG("Issuing MemoryPressureState::NoPressure");
+ NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure);
+ }
+
+ mLevel = aNewLevel;
+
+ if (!aIsInitialLevel) {
+ // Sysctls are already read by ::Init().
+ ReadSysctls();
+ MP_LOG("level sysctl: %d, available memory: %d percent", mLevelSysctl,
+ mAvailMemSysctl);
+ }
+ UpdateParentAnnotations();
+}
+
+/* virtual */
+// Add all annotations to the provided crash reporter instance.
+void nsAvailableMemoryWatcher::AddChildAnnotations(
+ const UniquePtr<ipc::CrashReporterHost>& aCrashReporter) {
+ aCrashReporter->AddAnnotation(CrashReporter::Annotation::MacMemoryPressure,
+ mLevelStr);
+ aCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr);
+ aCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr);
+ aCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::MacMemoryPressureCriticalTime,
+ mCriticalTimeStr);
+ aCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl);
+ aCrashReporter->AddAnnotation(
+ CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl);
+}
+
+void nsAvailableMemoryWatcher::LowMemoryResponse() {
+ if (mResponseMask & ResponseMask::eTabUnload) {
+ MP_LOG("Attempting tab unload");
+ mTabUnloader->UnloadTabAsync();
+ } else {
+ // Re-use OnUnloadAttemptCompleted() to issue the internal
+ // memory pressure event.
+ OnUnloadAttemptCompleted(NS_ERROR_NOT_AVAILABLE);
+ }
+}
+
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mLevel >= mResponseLevel);
+ LowMemoryResponse();
+ return NS_OK;
+}
+
+// Override OnUnloadAttemptCompleted() so that we can issue Gecko memory
+// pressure notifications only if eInternalMemoryPressure is set in
+// mResponseMask. When called from the tab unloader, an |aResult| value of
+// NS_OK indicates the tab unloader successfully unloaded a tab.
+// NS_ERROR_NOT_AVAILABLE indicates the tab unloader did not unload any tabs.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::OnUnloadAttemptCompleted(nsresult aResult) {
+ // On MacOS we don't access these members offthread; however we do on other
+ // OSes and so they are guarded by the mutex.
+ MutexAutoLock lock(mMutex);
+ switch (aResult) {
+ // A tab was unloaded successfully.
+ case NS_OK:
+ MP_LOG("Tab unloaded");
+ ++mNumOfTabUnloading;
+ break;
+
+ // Either the tab unloader found no unloadable tabs OR we've been called
+ // locally to explicitly issue the internal memory pressure event because
+ // tab unloading is disabled in |mResponseMask|. In either case, attempt
+ // to reduce memory use using the internal memory pressure notification.
+ case NS_ERROR_NOT_AVAILABLE:
+ if (mResponseMask & ResponseMask::eInternalMemoryPressure) {
+ ++mNumOfMemoryPressure;
+ MP_LOG("Tab not unloaded");
+ MP_LOG("Issuing MemoryPressureState::LowMemory");
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ }
+ break;
+
+ // There was a pending task to unload a tab.
+ case NS_ERROR_ABORT:
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected aResult");
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+ OnShutdown();
+ } else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ OnPrefChange();
+ }
+ return NS_OK;
+}
+
+void nsAvailableMemoryWatcher::OnShutdown() {
+ StopPolling();
+ Preferences::RemoveObserver(this, kResponseMask);
+ Preferences::RemoveObserver(this, kPollingIntervalMS);
+}
+
+void nsAvailableMemoryWatcher::OnPrefChange() {
+ MP_LOG("OnPrefChange()");
+ // Handle the polling interval changing.
+ uint32_t pollingInterval = Preferences::GetUint(kPollingIntervalMS);
+ if (pollingInterval != mPollingInterval) {
+ mPollingInterval = std::clamp(pollingInterval, kPollingIntervalMinMS,
+ kPollingIntervalMaxMS);
+ RestartPolling();
+ }
+
+ // Handle the response mask changing.
+ uint32_t responseMask = Preferences::GetUint(kResponseMask);
+ if (mResponseMask != responseMask) {
+ mResponseMask = std::min(responseMask, kResponseMaskMax);
+
+ // Do we need to turn on polling?
+ if (mResponseMask && (mLevel >= mResponseLevel) && !IsPolling()) {
+ StartPolling();
+ }
+
+ // Do we need to turn off polling?
+ if (!mResponseMask && IsPolling()) {
+ StopPolling();
+ }
+ }
+
+ // Handle the response level changing.
+ MacMemoryPressureLevel newResponseLevel;
+ if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) {
+ newResponseLevel = MacMemoryPressureLevel::Value::eWarning;
+ } else {
+ newResponseLevel = MacMemoryPressureLevel::Value::eCritical;
+ }
+ if (newResponseLevel == mResponseLevel) {
+ return;
+ }
+
+ // Do we need to turn on polling?
+ if (mResponseMask && (newResponseLevel <= mLevel)) {
+ UpdateLowMemoryTimeStamp();
+ LowMemoryResponse();
+ StartPolling();
+ }
+
+ // Do we need to turn off polling?
+ if (IsPolling() && (newResponseLevel > mLevel)) {
+ {
+ MutexAutoLock lock(mMutex);
+ RecordTelemetryEventOnHighMemory(lock);
+ }
+ StopPolling();
+ MP_LOG("Issuing MemoryPressureState::NoPressure");
+ NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure);
+ }
+ mResponseLevel = newResponseLevel;
+}
+
+void nsAvailableMemoryWatcher::StartPolling() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mTimer) {
+ MP_LOG("Starting poller");
+ mTimer = NS_NewTimer();
+ if (mTimer) {
+ mTimer->InitWithCallback(this, mPollingInterval,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }
+}
+
+void nsAvailableMemoryWatcher::StopPolling() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mTimer) {
+ MP_LOG("Pausing poller");
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsAvailableMemoryWatcher::RestartPolling() {
+ if (IsPolling()) {
+ StopPolling();
+ StartPolling();
+ } else {
+ MOZ_ASSERT(!mTimer);
+ }
+}
+
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAvailableMemoryWatcher");
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/AvailableMemoryWatcherUtils.h b/xpcom/base/AvailableMemoryWatcherUtils.h
new file mode 100644
index 0000000000..2d70fe5fd0
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcherUtils.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AvailableMemoryWatcherUtils_h
+#define mozilla_AvailableMemoryWatcherUtils_h
+
+#include "mozilla/Attributes.h"
+#include "nsISupportsUtils.h" // For nsresult
+
+namespace mozilla {
+
+struct MemoryInfo {
+ unsigned long memTotal;
+ unsigned long memAvailable;
+};
+// Check /proc/meminfo for low memory. Largely C method for reading
+// /proc/meminfo.
+MOZ_MAYBE_UNUSED
+static nsresult ReadMemoryFile(const char* meminfoPath, MemoryInfo& aResult) {
+ FILE* fd;
+ if ((fd = fopen(meminfoPath, "r")) == nullptr) {
+ // Meminfo somehow unreachable
+ return NS_ERROR_FAILURE;
+ }
+
+ char buff[128];
+
+ /* The first few lines of meminfo look something like this:
+ * MemTotal: 65663448 kB
+ * MemFree: 57368112 kB
+ * MemAvailable: 61852700 kB
+ * We mostly care about the available versus the total. We calculate our
+ * memory thresholds using this, and when memory drops below 5% we consider
+ * this to be a memory pressure event. In practice these lines aren't
+ * necessarily in order, but we can simply search for MemTotal
+ * and MemAvailable.
+ */
+ char namebuffer[20];
+ while ((fgets(buff, sizeof(buff), fd)) != nullptr) {
+ if (strstr(buff, "MemTotal:")) {
+ sscanf(buff, "%s %lu ", namebuffer, &aResult.memTotal);
+ }
+ if (strstr(buff, "MemAvailable:")) {
+ sscanf(buff, "%s %lu ", namebuffer, &aResult.memAvailable);
+ }
+ }
+ fclose(fd);
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_AvailableMemoryWatcherUtils_h
diff --git a/xpcom/base/AvailableMemoryWatcherWin.cpp b/xpcom/base/AvailableMemoryWatcherWin.cpp
new file mode 100644
index 0000000000..cd027366de
--- /dev/null
+++ b/xpcom/base/AvailableMemoryWatcherWin.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AvailableMemoryWatcher.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsAppRunner.h"
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#include "nsIObserver.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include <memoryapi.h>
+
+extern mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed>
+ sNumLowPhysicalMemEvents;
+
+namespace mozilla {
+
+// This class is used to monitor low memory events delivered by Windows via
+// memory resource notification objects. When we enter a low memory scenario
+// the LowMemoryCallback() is invoked by Windows. This initial call triggers
+// an nsITimer that polls to see when the low memory condition has been lifted.
+// When it has, we'll stop polling and start waiting for the next
+// LowMemoryCallback(). Meanwhile, the polling may be stopped and restarted by
+// user-interaction events from the observer service.
+class nsAvailableMemoryWatcher final : public nsITimerCallback,
+ public nsINamed,
+ public nsAvailableMemoryWatcherBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsAvailableMemoryWatcher();
+ nsresult Init() override;
+
+ private:
+ static VOID CALLBACK LowMemoryCallback(PVOID aContext, BOOLEAN aIsTimer);
+ static void RecordLowMemoryEvent();
+ static bool IsCommitSpaceLow();
+
+ ~nsAvailableMemoryWatcher();
+ bool RegisterMemoryResourceHandler(const MutexAutoLock& aLock)
+ MOZ_REQUIRES(mMutex);
+ void UnregisterMemoryResourceHandler(const MutexAutoLock&)
+ MOZ_REQUIRES(mMutex);
+ void MaybeSaveMemoryReport(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void Shutdown(const MutexAutoLock& aLock) MOZ_REQUIRES(mMutex);
+ bool ListenForLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void OnLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void OnHighMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void StartPollingIfUserInteracting(const MutexAutoLock& aLock)
+ MOZ_REQUIRES(mMutex);
+ void StopPolling(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void StopPollingIfUserIdle(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+ void OnUserInteracting(const MutexAutoLock&) MOZ_REQUIRES(mMutex);
+
+ nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex);
+ nsAutoHandle mLowMemoryHandle MOZ_GUARDED_BY(mMutex);
+ HANDLE mWaitHandle MOZ_GUARDED_BY(mMutex);
+ bool mPolling MOZ_GUARDED_BY(mMutex);
+
+ // Indicates whether to start a timer when user interaction is notified.
+ // This flag is needed because the low-memory callback may be triggered when
+ // the user is inactive and we want to delay-start the timer.
+ bool mNeedToRestartTimerOnUserInteracting MOZ_GUARDED_BY(mMutex);
+ // Indicate that the available commit space is low. The timer handler needs
+ // this flag because it is triggered by the low physical memory regardless
+ // of the available commit space.
+ bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex);
+
+ bool mSavedReport MOZ_GUARDED_BY(mMutex);
+ bool mIsShutdown MOZ_GUARDED_BY(mMutex);
+
+ // Members below this line are used only in the main thread.
+ // No lock is needed.
+
+ // Don't fire a low-memory notification more often than this interval.
+ uint32_t mPollingInterval;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher,
+ nsAvailableMemoryWatcherBase, nsIObserver,
+ nsITimerCallback, nsINamed)
+
+nsAvailableMemoryWatcher::nsAvailableMemoryWatcher()
+ : mWaitHandle(nullptr),
+ mPolling(false),
+ mNeedToRestartTimerOnUserInteracting(false),
+ mUnderMemoryPressure(false),
+ mSavedReport(false),
+ mIsShutdown(false),
+ mPollingInterval(0) {}
+
+nsresult nsAvailableMemoryWatcher::Init() {
+ nsresult rv = nsAvailableMemoryWatcherBase::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MutexAutoLock lock(mMutex);
+ mTimer = NS_NewTimer();
+ if (!mTimer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Use a very short interval for GTest to verify the timer's behavior.
+ mPollingInterval = gIsGtest ? 10 : 10000;
+
+ if (!RegisterMemoryResourceHandler(lock)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsAvailableMemoryWatcher::~nsAvailableMemoryWatcher() {
+ // These handles should have been released during the shutdown phase.
+ MOZ_ASSERT(!mLowMemoryHandle);
+ MOZ_ASSERT(!mWaitHandle);
+}
+
+// static
+VOID CALLBACK nsAvailableMemoryWatcher::LowMemoryCallback(PVOID aContext,
+ BOOLEAN aIsTimer) {
+ if (aIsTimer) {
+ return;
+ }
+
+ // The |watcher| was addref'ed when we registered the wait handle in
+ // ListenForLowMemory(). It is decremented when exiting the function,
+ // so please make sure we unregister the wait handle after this line.
+ RefPtr<nsAvailableMemoryWatcher> watcher =
+ already_AddRefed<nsAvailableMemoryWatcher>(
+ static_cast<nsAvailableMemoryWatcher*>(aContext));
+
+ MutexAutoLock lock(watcher->mMutex);
+ if (watcher->mIsShutdown) {
+ // mWaitHandle should have been unregistered during shutdown
+ MOZ_ASSERT(!watcher->mWaitHandle);
+ return;
+ }
+
+ ::UnregisterWait(watcher->mWaitHandle);
+ watcher->mWaitHandle = nullptr;
+
+ // On Windows, memory allocations fails when the available commit space is
+ // not sufficient. It's possible that this callback function is invoked
+ // but there is still commit space enough for the application to continue
+ // to run. In such a case, there is no strong need to trigger the memory
+ // pressure event. So we trigger the event only when the available commit
+ // space is low.
+ if (IsCommitSpaceLow()) {
+ watcher->OnLowMemory(lock);
+ } else {
+ // Since we have unregistered the callback, we need to start a timer to
+ // continue watching memory.
+ watcher->StartPollingIfUserInteracting(lock);
+ }
+}
+
+// static
+void nsAvailableMemoryWatcher::RecordLowMemoryEvent() {
+ sNumLowPhysicalMemEvents++;
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::LowPhysicalMemoryEvents,
+ sNumLowPhysicalMemEvents);
+}
+
+bool nsAvailableMemoryWatcher::RegisterMemoryResourceHandler(
+ const MutexAutoLock& aLock) {
+ mLowMemoryHandle.own(
+ ::CreateMemoryResourceNotification(LowMemoryResourceNotification));
+
+ if (!mLowMemoryHandle) {
+ return false;
+ }
+
+ return ListenForLowMemory(aLock);
+}
+
+void nsAvailableMemoryWatcher::UnregisterMemoryResourceHandler(
+ const MutexAutoLock&) {
+ if (mWaitHandle) {
+ bool res = ::UnregisterWait(mWaitHandle);
+ if (res || ::GetLastError() != ERROR_IO_PENDING) {
+ // We decrement the refcount only when we're sure the LowMemoryCallback()
+ // callback won't be invoked, otherwise the callback will do it
+ this->Release();
+ }
+ mWaitHandle = nullptr;
+ }
+
+ mLowMemoryHandle.reset();
+}
+
+void nsAvailableMemoryWatcher::Shutdown(const MutexAutoLock& aLock) {
+ mIsShutdown = true;
+ mNeedToRestartTimerOnUserInteracting = false;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ UnregisterMemoryResourceHandler(aLock);
+}
+
+bool nsAvailableMemoryWatcher::ListenForLowMemory(const MutexAutoLock&) {
+ if (mLowMemoryHandle && !mWaitHandle) {
+ // We're giving ownership of this object to the LowMemoryCallback(). We
+ // increment the count here so that the object is kept alive until the
+ // callback decrements it.
+ this->AddRef();
+ bool res = ::RegisterWaitForSingleObject(
+ &mWaitHandle, mLowMemoryHandle, LowMemoryCallback, this, INFINITE,
+ WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE);
+ if (!res) {
+ // We couldn't register the callback, decrement the count
+ this->Release();
+ }
+ // Once we register the wait handle, we no longer need to start
+ // the timer because we can continue watching memory via callback.
+ mNeedToRestartTimerOnUserInteracting = false;
+ return res;
+ }
+
+ return false;
+}
+
+void nsAvailableMemoryWatcher::MaybeSaveMemoryReport(const MutexAutoLock&) {
+ if (mSavedReport) {
+ return;
+ }
+
+ if (nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1")) {
+ mSavedReport = NS_SUCCEEDED(cr->SaveMemoryReport());
+ }
+}
+
+void nsAvailableMemoryWatcher::OnLowMemory(const MutexAutoLock& aLock) {
+ mUnderMemoryPressure = true;
+ RecordLowMemoryEvent();
+
+ if (NS_IsMainThread()) {
+ MaybeSaveMemoryReport(aLock);
+ UpdateLowMemoryTimeStamp();
+ {
+ // Don't invoke UnloadTabAsync() with the lock to avoid deadlock
+ // because nsAvailableMemoryWatcher::Notify may be invoked while
+ // running the method.
+ MutexAutoUnlock unlock(mMutex);
+ mTabUnloader->UnloadTabAsync();
+ }
+ } else {
+ // SaveMemoryReport and mTabUnloader needs to be run in the main thread
+ // (See nsMemoryReporterManager::GetReportsForThisProcessExtended)
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsAvailableMemoryWatcher::OnLowMemory", [self = RefPtr{this}]() {
+ {
+ MutexAutoLock lock(self->mMutex);
+ self->MaybeSaveMemoryReport(lock);
+ self->UpdateLowMemoryTimeStamp();
+ }
+ self->mTabUnloader->UnloadTabAsync();
+ }));
+ }
+
+ StartPollingIfUserInteracting(aLock);
+}
+
+void nsAvailableMemoryWatcher::OnHighMemory(const MutexAutoLock& aLock) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mUnderMemoryPressure) {
+ RecordTelemetryEventOnHighMemory(aLock);
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure);
+ }
+
+ mUnderMemoryPressure = false;
+ mSavedReport = false; // Will save a new report if memory gets low again
+ StopPolling(aLock);
+ ListenForLowMemory(aLock);
+}
+
+// static
+bool nsAvailableMemoryWatcher::IsCommitSpaceLow() {
+ // Other options to get the available page file size:
+ // - GetPerformanceInfo
+ // Too slow, don't use it.
+ // - PdhCollectQueryData and PdhGetRawCounterValue
+ // Faster than GetPerformanceInfo, but slower than GlobalMemoryStatusEx.
+ // - NtQuerySystemInformation(SystemMemoryUsageInformation)
+ // Faster than GlobalMemoryStatusEx, but undocumented.
+ MEMORYSTATUSEX memStatus = {sizeof(memStatus)};
+ if (!::GlobalMemoryStatusEx(&memStatus)) {
+ return false;
+ }
+
+ constexpr size_t kBytesPerMB = 1024 * 1024;
+ return (memStatus.ullAvailPageFile / kBytesPerMB) <
+ StaticPrefs::browser_low_commit_space_threshold_mb();
+}
+
+void nsAvailableMemoryWatcher::StartPollingIfUserInteracting(
+ const MutexAutoLock&) {
+ // When the user is inactive, we mark the flag to delay-start
+ // the timer when the user becomes active later.
+ mNeedToRestartTimerOnUserInteracting = true;
+
+ if (mInteracting && !mPolling) {
+ if (NS_SUCCEEDED(mTimer->InitWithCallback(
+ this, mPollingInterval, nsITimer::TYPE_REPEATING_SLACK))) {
+ mPolling = true;
+ }
+ }
+}
+
+void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&) {
+ mTimer->Cancel();
+ mPolling = false;
+}
+
+void nsAvailableMemoryWatcher::StopPollingIfUserIdle(
+ const MutexAutoLock& aLock) {
+ if (!mInteracting) {
+ StopPolling(aLock);
+ }
+}
+
+void nsAvailableMemoryWatcher::OnUserInteracting(const MutexAutoLock& aLock) {
+ if (mNeedToRestartTimerOnUserInteracting) {
+ StartPollingIfUserInteracting(aLock);
+ }
+}
+
+// Timer callback, polls the low memory resource notification to detect when
+// we've freed enough memory or if we have to send more memory pressure events.
+// Polling stops automatically when the user is not interacting with the UI.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) {
+ MutexAutoLock lock(mMutex);
+ StopPollingIfUserIdle(lock);
+
+ if (IsCommitSpaceLow()) {
+ OnLowMemory(lock);
+ } else {
+ OnHighMemory(lock);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAvailableMemoryWatcher");
+ return NS_OK;
+}
+
+// Observer service callback, used to stop the polling timer when the user
+// stops interacting with Firefox and resuming it when they interact again.
+// Also used to shut down the service if the application is quitting.
+NS_IMETHODIMP
+nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+ Shutdown(lock);
+ } else if (strcmp(aTopic, "user-interaction-active") == 0) {
+ OnUserInteracting(lock);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsAvailableMemoryWatcherBase> CreateAvailableMemoryWatcher() {
+ RefPtr watcher(new nsAvailableMemoryWatcher);
+ if (NS_FAILED(watcher->Init())) {
+ return do_AddRef(new nsAvailableMemoryWatcherBase); // fallback
+ }
+ return watcher.forget();
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp
new file mode 100644
index 0000000000..700785736d
--- /dev/null
+++ b/xpcom/base/ClearOnShutdown.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace ClearOnShutdown_Internal {
+
+Array<StaticAutoPtr<ShutdownList>,
+ static_cast<size_t>(ShutdownPhase::ShutdownPhase_Length)>
+ sShutdownObservers;
+ShutdownPhase sCurrentClearOnShutdownPhase = ShutdownPhase::NotInShutdown;
+
+void InsertIntoShutdownList(ShutdownObserver* aObserver, ShutdownPhase aPhase) {
+ // Adding a ClearOnShutdown for a "past" phase is an error.
+ if (PastShutdownPhase(aPhase)) {
+ MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared");
+ aObserver->Shutdown();
+ delete aObserver;
+ return;
+ }
+
+ if (!(sShutdownObservers[static_cast<size_t>(aPhase)])) {
+ sShutdownObservers[static_cast<size_t>(aPhase)] = new ShutdownList();
+ }
+ sShutdownObservers[static_cast<size_t>(aPhase)]->insertBack(aObserver);
+}
+
+} // namespace ClearOnShutdown_Internal
+
+// Called by AdvanceShutdownPhase each time we switch a phase. Will null out
+// pointers added by ClearOnShutdown for all phases up to and including aPhase.
+void KillClearOnShutdown(ShutdownPhase aPhase) {
+ using namespace ClearOnShutdown_Internal;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ // Shutdown only goes one direction...
+ MOZ_ASSERT(!PastShutdownPhase(aPhase));
+
+ // Set the phase before notifying observers to make sure that they can't run
+ // any code which isn't allowed to run after the start of this phase.
+ sCurrentClearOnShutdownPhase = aPhase;
+
+ // It's impossible to add an entry for a "past" phase; this is blocked in
+ // ClearOnShutdown, but clear them out anyways in case there are phases
+ // that weren't passed to KillClearOnShutdown.
+ for (size_t phase = static_cast<size_t>(ShutdownPhase::First);
+ phase <= static_cast<size_t>(aPhase); phase++) {
+ if (sShutdownObservers[static_cast<size_t>(phase)]) {
+ while (ShutdownObserver* observer =
+ sShutdownObservers[static_cast<size_t>(phase)]->popLast()) {
+ observer->Shutdown();
+ delete observer;
+ }
+ sShutdownObservers[static_cast<size_t>(phase)] = nullptr;
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/ClearOnShutdown.h b/xpcom/base/ClearOnShutdown.h
new file mode 100644
index 0000000000..f927e3334a
--- /dev/null
+++ b/xpcom/base/ClearOnShutdown.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ClearOnShutdown_h
+#define mozilla_ClearOnShutdown_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Array.h"
+#include "ShutdownPhase.h"
+#include "MainThreadUtils.h"
+
+#include <functional>
+
+/*
+ * This header exports two public methods in the mozilla namespace:
+ *
+ * template<class SmartPtr>
+ * void ClearOnShutdown(SmartPtr *aPtr,
+ * aPhase=ShutdownPhase::XPCOMShutdownFinal)
+ *
+ * This function takes a pointer to a smart pointer and nulls the smart pointer
+ * on shutdown (and a particular phase of shutdown as needed). If a phase
+ * is specified, the ptr will be cleared at the start of that phase. Also,
+ * if a phase has already occurred when ClearOnShutdown() is called it will
+ * cause a MOZ_ASSERT. In case a phase is not explicitly cleared we will
+ * clear it on the next phase that occurs.
+ *
+ * This is useful if you have a global smart pointer object which you don't
+ * want to "leak" on shutdown.
+ *
+ * Although ClearOnShutdown will work with any smart pointer (i.e., nsCOMPtr,
+ * RefPtr, StaticRefPtr, and StaticAutoPtr), you probably want to
+ * use it only with StaticRefPtr and StaticAutoPtr. There is no way to undo a
+ * call to ClearOnShutdown, so you can call it only on smart pointers which you
+ * know will live until the program shuts down. In practice, these are likely
+ * global variables, which should be Static{Ref,Auto}Ptr.
+ *
+ * template <typename CallableT>
+ * void RunOnShutdown(CallableT&& aCallable,
+ * aPhase = ShutdownPhase::XPCOMShutdownFinal)
+ *
+ * This function takes a callable and executes it upon shutdown at the start of
+ * the specified phase. If the phase has already occurred when RunOnShutdown()
+ * is called, it will cause a MOZ_ASSERT. In case a phase is not explicitly
+ * cleared, we will clear it on the next phase that occurs.
+ *
+ * ClearOnShutdown and RunOnShutdown are both currently main-thread only because
+ * we don't want to accidentally free an object from a different thread than the
+ * one it was created on.
+ */
+
+namespace mozilla {
+
+namespace ClearOnShutdown_Internal {
+
+class ShutdownObserver : public LinkedListElement<ShutdownObserver> {
+ public:
+ virtual void Shutdown() = 0;
+ virtual ~ShutdownObserver() = default;
+};
+
+template <class SmartPtr>
+class PointerClearer : public ShutdownObserver {
+ public:
+ explicit PointerClearer(SmartPtr* aPtr) : mPtr(aPtr) {}
+
+ virtual void Shutdown() override {
+ if (mPtr) {
+ *mPtr = nullptr;
+ }
+ }
+
+ private:
+ SmartPtr* mPtr;
+};
+
+class FunctionInvoker : public ShutdownObserver {
+ public:
+ template <typename CallableT>
+ explicit FunctionInvoker(CallableT&& aCallable)
+ : mCallable(std::forward<CallableT>(aCallable)) {}
+
+ virtual void Shutdown() override {
+ if (!mCallable) {
+ return;
+ }
+
+ mCallable();
+ }
+
+ private:
+ std::function<void()> mCallable;
+};
+
+void InsertIntoShutdownList(ShutdownObserver* aShutdownObserver,
+ ShutdownPhase aPhase);
+
+typedef LinkedList<ShutdownObserver> ShutdownList;
+extern Array<StaticAutoPtr<ShutdownList>,
+ static_cast<size_t>(ShutdownPhase::ShutdownPhase_Length)>
+ sShutdownObservers;
+extern ShutdownPhase sCurrentClearOnShutdownPhase;
+
+} // namespace ClearOnShutdown_Internal
+
+template <class SmartPtr>
+inline void ClearOnShutdown(
+ SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) {
+ using namespace ClearOnShutdown_Internal;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length);
+
+ InsertIntoShutdownList(new PointerClearer<SmartPtr>(aPtr), aPhase);
+}
+
+template <typename CallableT>
+inline void RunOnShutdown(
+ CallableT&& aCallable,
+ ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) {
+ using namespace ClearOnShutdown_Internal;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length);
+
+ InsertIntoShutdownList(
+ new FunctionInvoker(std::forward<CallableT>(aCallable)), aPhase);
+}
+
+inline bool PastShutdownPhase(ShutdownPhase aPhase) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return size_t(ClearOnShutdown_Internal::sCurrentClearOnShutdownPhase) >=
+ size_t(aPhase);
+}
+
+// Called by AdvanceShutdownPhase each time we switch a phase. Will null out
+// pointers added by ClearOnShutdown for all phases up to and including aPhase.
+void KillClearOnShutdown(ShutdownPhase aPhase);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h
new file mode 100644
index 0000000000..821bf2c73c
--- /dev/null
+++ b/xpcom/base/CodeAddressService.h
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CodeAddressService_h__
+#define CodeAddressService_h__
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StackWalk.h"
+
+namespace mozilla {
+
+namespace detail {
+
+template <class AllocPolicy>
+class CodeAddressServiceAllocPolicy : public AllocPolicy {
+ public:
+ char* strdup_(const char* aStr) {
+ char* s = AllocPolicy::template pod_malloc<char>(strlen(aStr) + 1);
+ if (!s) {
+ MOZ_CRASH("CodeAddressService OOM");
+ }
+ strcpy(s, aStr);
+ return s;
+ }
+};
+
+// Default implementation of DescribeCodeAddressLock.
+struct DefaultDescribeCodeAddressLock {
+ static void Unlock() {}
+ static void Lock() {}
+ // Because CodeAddressService asserts that IsLocked() is true, returning true
+ // here is a sensible default when there is no relevant lock.
+ static bool IsLocked() { return true; }
+};
+
+} // namespace detail
+
+// This class is used to print details about code locations.
+//
+// |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h.
+//
+// |DescribeCodeAddressLock| is needed when the callers may be holding a lock
+// used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement
+// static methods IsLocked(), Unlock() and Lock().
+template <class AllocPolicy_ = MallocAllocPolicy,
+ class DescribeCodeAddressLock =
+ detail::DefaultDescribeCodeAddressLock>
+class CodeAddressService
+ : private detail::CodeAddressServiceAllocPolicy<AllocPolicy_> {
+ protected:
+ // GetLocation() is the key function in this class. It's basically a wrapper
+ // around MozDescribeCodeAddress.
+ //
+ // However, MozDescribeCodeAddress is very slow on some platforms, and we
+ // have lots of repeated (i.e. same PC) calls to it. So we do some caching
+ // of results. Each cached result includes two strings (|mFunction| and
+ // |mLibrary|), so we also optimize them for space in the following ways.
+ //
+ // - The number of distinct library names is small, e.g. a few dozen. There
+ // is lots of repetition, especially of libxul. So we intern them in their
+ // own table, which saves space over duplicating them for each cache entry.
+ //
+ // - The number of distinct function names is much higher, so we duplicate
+ // them in each cache entry. That's more space-efficient than interning
+ // because entries containing single-occurrence function names are quickly
+ // overwritten, and their copies released. In addition, empty function
+ // names are common, so we use nullptr to represent them compactly.
+
+ using AllocPolicy = detail::CodeAddressServiceAllocPolicy<AllocPolicy_>;
+ using StringHashSet = HashSet<const char*, CStringHasher, AllocPolicy>;
+
+ StringHashSet mLibraryStrings;
+
+ struct Entry : private AllocPolicy {
+ const void* mPc;
+ char* mFunction; // owned by the Entry; may be null
+ const char* mLibrary; // owned by mLibraryStrings; never null
+ // in a non-empty entry is in use
+ ptrdiff_t mLOffset;
+ char* mFileName; // owned by the Entry; may be null
+ uint32_t mLineNo : 31;
+ uint32_t mInUse : 1; // is the entry used?
+
+ Entry()
+ : mPc(0),
+ mFunction(nullptr),
+ mLibrary(nullptr),
+ mLOffset(0),
+ mFileName(nullptr),
+ mLineNo(0),
+ mInUse(0) {}
+
+ ~Entry() {
+ // We don't free mLibrary because it's externally owned.
+ AllocPolicy::free_(mFunction);
+ AllocPolicy::free_(mFileName);
+ }
+
+ void Replace(const void* aPc, const char* aFunction, const char* aLibrary,
+ ptrdiff_t aLOffset, const char* aFileName,
+ unsigned long aLineNo) {
+ mPc = aPc;
+
+ // Convert "" to nullptr. Otherwise, make a copy of the name.
+ AllocPolicy::free_(mFunction);
+ mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction);
+ AllocPolicy::free_(mFileName);
+ mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(aFileName);
+
+ mLibrary = aLibrary;
+ mLOffset = aLOffset;
+ mLineNo = aLineNo;
+
+ mInUse = 1;
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ // Don't measure mLibrary because it's externally owned.
+ size_t n = 0;
+ n += aMallocSizeOf(mFunction);
+ n += aMallocSizeOf(mFileName);
+ return n;
+ }
+ };
+
+ const char* InternLibraryString(const char* aString) {
+ auto p = mLibraryStrings.lookupForAdd(aString);
+ if (p) {
+ return *p;
+ }
+
+ const char* newString = AllocPolicy::strdup_(aString);
+ if (!mLibraryStrings.add(p, newString)) {
+ MOZ_CRASH("CodeAddressService OOM");
+ }
+ return newString;
+ }
+
+ Entry& GetEntry(const void* aPc) {
+ MOZ_ASSERT(DescribeCodeAddressLock::IsLocked());
+
+ uint32_t index = HashGeneric(aPc) & kMask;
+ MOZ_ASSERT(index < kNumEntries);
+ Entry& entry = mEntries[index];
+
+ if (!entry.mInUse || entry.mPc != aPc) {
+ mNumCacheMisses++;
+
+ // MozDescribeCodeAddress can (on Linux) acquire a lock inside
+ // the shared library loader. Another thread might call malloc
+ // while holding that lock (when loading a shared library). So
+ // we have to exit the lock around this call. For details, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
+ MozCodeAddressDetails details;
+ {
+ DescribeCodeAddressLock::Unlock();
+ (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details);
+ DescribeCodeAddressLock::Lock();
+ }
+
+ const char* library = InternLibraryString(details.library);
+ entry.Replace(aPc, details.function, library, details.loffset,
+ details.filename, details.lineno);
+
+ } else {
+ mNumCacheHits++;
+ }
+
+ MOZ_ASSERT(entry.mPc == aPc);
+
+ return entry;
+ }
+
+ // A direct-mapped cache. When doing dmd::Analyze() just after starting
+ // desktop Firefox (which is similar to analyzing after a longer-running
+ // session, thanks to the limit on how many records we print), a cache with
+ // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit
+ // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB
+ // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms).
+ static const size_t kNumEntries = 1 << 12;
+ static const size_t kMask = kNumEntries - 1;
+ Entry mEntries[kNumEntries];
+
+ size_t mNumCacheHits;
+ size_t mNumCacheMisses;
+
+ public:
+ CodeAddressService()
+ : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {}
+
+ ~CodeAddressService() {
+ for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) {
+ AllocPolicy::free_(const_cast<char*>(iter.get()));
+ }
+ }
+
+ // Returns the minimum number of characters necessary to format the frame
+ // information, without the terminating null. The buffer will be truncated
+ // if the returned value is greater than aBufLen-1.
+ int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf,
+ size_t aBufLen) {
+ Entry& entry = GetEntry(aPc);
+ return MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc,
+ entry.mFunction, entry.mLibrary, entry.mLOffset,
+ entry.mFileName, entry.mLineNo);
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ for (uint32_t i = 0; i < kNumEntries; i++) {
+ n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ n += mLibraryStrings.shallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) {
+ n += aMallocSizeOf(iter.get());
+ }
+
+ return n;
+ }
+
+ size_t CacheCapacity() const { return kNumEntries; }
+
+ size_t CacheCount() const {
+ size_t n = 0;
+ for (size_t i = 0; i < kNumEntries; i++) {
+ if (mEntries[i].mInUse) {
+ n++;
+ }
+ }
+ return n;
+ }
+
+ size_t NumCacheHits() const { return mNumCacheHits; }
+ size_t NumCacheMisses() const { return mNumCacheMisses; }
+};
+
+} // namespace mozilla
+
+#endif // CodeAddressService_h__
diff --git a/xpcom/base/CountingAllocatorBase.h b/xpcom/base/CountingAllocatorBase.h
new file mode 100644
index 0000000000..02d74f3223
--- /dev/null
+++ b/xpcom/base/CountingAllocatorBase.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CountingAllocatorBase_h
+#define CountingAllocatorBase_h
+
+#include <cstdlib>
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/mozalloc.h"
+#include "nsIMemoryReporter.h"
+
+namespace mozilla {
+
+// This CRTP class handles several details of wrapping allocators and should
+// be preferred to manually counting with MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC
+// and MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE. The typical use is in a memory
+// reporter for a particular third party library:
+//
+// class MyMemoryReporter : public CountingAllocatorBase<MyMemoryReporter>
+// {
+// ...
+// NS_IMETHOD
+// CollectReports(nsIHandleReportCallback* aHandleReport,
+// nsISupports* aData, bool aAnonymize) override
+// {
+// MOZ_COLLECT_REPORT(
+// "explicit/path/to/somewhere", KIND_HEAP, UNITS_BYTES,
+// MemoryAllocated(),
+// "A description of what we are reporting.");
+//
+// return NS_OK;
+// }
+// };
+//
+// ...somewhere later in the code...
+// SetThirdPartyMemoryFunctions(MyMemoryReporter::CountingAlloc,
+// MyMemoryReporter::CountingFree);
+template <typename T>
+class CountingAllocatorBase {
+ public:
+ CountingAllocatorBase() {
+#ifdef DEBUG
+ // There must be only one instance of this class, due to |sAmount| being
+ // static.
+ static bool hasRun = false;
+ MOZ_ASSERT(!hasRun);
+ hasRun = true;
+#endif
+ }
+
+ static size_t MemoryAllocated() { return sAmount; }
+
+ static void* CountingMalloc(size_t size) {
+ void* p = malloc(size);
+ sAmount += MallocSizeOfOnAlloc(p);
+ return p;
+ }
+
+ static void* CountingCalloc(size_t nmemb, size_t size) {
+ void* p = calloc(nmemb, size);
+ sAmount += MallocSizeOfOnAlloc(p);
+ return p;
+ }
+
+ static void* CountingRealloc(void* p, size_t size) {
+ size_t oldsize = MallocSizeOfOnFree(p);
+ void* pnew = realloc(p, size);
+ if (pnew) {
+ size_t newsize = MallocSizeOfOnAlloc(pnew);
+ sAmount += newsize - oldsize;
+ } else if (size == 0) {
+ // We asked for a 0-sized (re)allocation of some existing pointer
+ // and received NULL in return. 0-sized allocations are permitted
+ // to either return NULL or to allocate a unique object per call (!).
+ // For a malloc implementation that chooses the second strategy,
+ // that allocation may fail (unlikely, but possible).
+ //
+ // Given a NULL return value and an allocation size of 0, then, we
+ // don't know if that means the original pointer was freed or if
+ // the allocation of the unique object failed. If the original
+ // pointer was freed, then we have nothing to do here. If the
+ // allocation of the unique object failed, the original pointer is
+ // still valid and we ought to undo the decrement from above.
+ // However, we have no way of knowing how the underlying realloc
+ // implementation is behaving. Assuming that the original pointer
+ // was freed is the safest course of action. We do, however, need
+ // to note that we freed memory.
+ sAmount -= oldsize;
+ } else {
+ // realloc failed. The amount allocated hasn't changed.
+ }
+ return pnew;
+ }
+
+ // Some library code expects that realloc(x, 0) will free x, which is not
+ // the behavior of the version of jemalloc we're using, so this wrapped
+ // version of realloc is needed.
+ static void* CountingFreeingRealloc(void* p, size_t size) {
+ if (size == 0) {
+ CountingFree(p);
+ return nullptr;
+ }
+ return CountingRealloc(p, size);
+ }
+
+ static void CountingFree(void* p) {
+ sAmount -= MallocSizeOfOnFree(p);
+ free(p);
+ }
+
+ // Infallible-allocation wrappers for the counting malloc/calloc/realloc
+ // functions, for clients that don't safely handle allocation failures
+ // themselves.
+ static void* InfallibleCountingMalloc(size_t size) {
+ void* p = moz_xmalloc(size);
+ sAmount += MallocSizeOfOnAlloc(p);
+ return p;
+ }
+
+ static void* InfallibleCountingCalloc(size_t nmemb, size_t size) {
+ void* p = moz_xcalloc(nmemb, size);
+ sAmount += MallocSizeOfOnAlloc(p);
+ return p;
+ }
+
+ static void* InfallibleCountingRealloc(void* p, size_t size) {
+ size_t oldsize = MallocSizeOfOnFree(p);
+ void* pnew = moz_xrealloc(p, size);
+ if (pnew) {
+ size_t newsize = MallocSizeOfOnAlloc(pnew);
+ sAmount += newsize - oldsize;
+ } else if (size == 0) {
+ // See comment in CountingRealloc above.
+ sAmount -= oldsize;
+ } else {
+ // realloc failed. The amount allocated hasn't changed.
+ }
+ return pnew;
+ }
+
+ private:
+ // |sAmount| can be (implicitly) accessed by multiple threads, so it
+ // must be thread-safe. It may be written during GC, so accesses are not
+ // recorded.
+ typedef Atomic<size_t, SequentiallyConsistent> AmountType;
+ static inline AmountType sAmount{0};
+
+ MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc)
+ MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree)
+};
+
+} // namespace mozilla
+
+#endif // CountingAllocatorBase_h
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
new file mode 100644
index 0000000000..b65acebe2e
--- /dev/null
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/CycleCollectedJSContext.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "js/Debug.h"
+#include "js/GCAPI.h"
+#include "js/Utility.h"
+#include "jsapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/DebuggerOnGCRunnable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/FinalizationRegistryBinding.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/PromiseRejectionEvent.h"
+#include "mozilla/dom/PromiseRejectionEventBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/UserActivation.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCycleCollector.h"
+#include "nsDOMJSUtils.h"
+#include "nsDOMMutationObserver.h"
+#include "nsJSUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsStringBuffer.h"
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "nsWrapperCache.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+CycleCollectedJSContext::CycleCollectedJSContext()
+ : mRuntime(nullptr),
+ mJSContext(nullptr),
+ mDoingStableStates(false),
+ mTargetedMicroTaskRecursionDepth(0),
+ mMicroTaskLevel(0),
+ mSuppressionGeneration(0),
+ mDebuggerRecursionDepth(0),
+ mMicroTaskRecursionDepth(0),
+ mFinalizationRegistryCleanup(this) {
+ MOZ_COUNT_CTOR(CycleCollectedJSContext);
+
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ mOwningThread = thread.forget().downcast<nsThread>().take();
+ MOZ_RELEASE_ASSERT(mOwningThread);
+}
+
+CycleCollectedJSContext::~CycleCollectedJSContext() {
+ MOZ_COUNT_DTOR(CycleCollectedJSContext);
+ // If the allocation failed, here we are.
+ if (!mJSContext) {
+ return;
+ }
+
+ JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr);
+
+ JS_SetContextPrivate(mJSContext, nullptr);
+
+ mRuntime->SetContext(nullptr);
+ mRuntime->Shutdown(mJSContext);
+
+ // Last chance to process any events.
+ CleanupIDBTransactions(mBaseRecursionDepth);
+ MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
+
+ ProcessStableStateQueue();
+ MOZ_ASSERT(mStableStateEvents.IsEmpty());
+
+ // Clear mPendingException first, since it might be cycle collected.
+ mPendingException = nullptr;
+
+ MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
+ MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
+
+ mUncaughtRejections.reset();
+ mConsumedRejections.reset();
+
+ mAboutToBeNotifiedRejectedPromises.Clear();
+ mPendingUnhandledRejections.Clear();
+
+ mFinalizationRegistryCleanup.Destroy();
+
+ JS_DestroyContext(mJSContext);
+ mJSContext = nullptr;
+
+ nsCycleCollector_forgetJSContext();
+
+ mozilla::dom::DestroyScriptSettings();
+
+ mOwningThread->SetScriptObserver(nullptr);
+ NS_RELEASE(mOwningThread);
+
+ delete mRuntime;
+ mRuntime = nullptr;
+}
+
+nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime,
+ uint32_t aMaxBytes) {
+ MOZ_ASSERT(!mJSContext);
+
+ mozilla::dom::InitScriptSettings();
+ mJSContext = JS_NewContext(aMaxBytes, aParentRuntime);
+ if (!mJSContext) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mRuntime = CreateRuntime(mJSContext);
+ mRuntime->SetContext(this);
+
+ mOwningThread->SetScriptObserver(this);
+ // The main thread has a base recursion depth of 0, workers of 1.
+ mBaseRecursionDepth = RecursionDepth();
+
+ NS_GetCurrentThread()->SetCanInvokeJS(true);
+
+ JS::SetJobQueue(mJSContext, this);
+ JS::SetPromiseRejectionTrackerCallback(mJSContext,
+ PromiseRejectionTrackerCallback, this);
+ mUncaughtRejections.init(mJSContext,
+ JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
+ js::SystemAllocPolicy()));
+ mConsumedRejections.init(mJSContext,
+ JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(
+ js::SystemAllocPolicy()));
+
+ mFinalizationRegistryCleanup.Init();
+
+ // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*).
+ JS_SetContextPrivate(mJSContext, static_cast<PerThreadAtomCache*>(this));
+
+ nsCycleCollector_registerJSContext(this);
+
+ return NS_OK;
+}
+
+/* static */
+CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) {
+ // Cast from void* matching JS_SetContextPrivate.
+ auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
+ // Down cast.
+ return static_cast<CycleCollectedJSContext*>(atomCache);
+}
+
+size_t CycleCollectedJSContext::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return 0;
+}
+
+class PromiseJobRunnable final : public MicroTaskRunnable {
+ public:
+ PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback,
+ JS::HandleObject aCallbackGlobal,
+ JS::HandleObject aAllocationSite,
+ nsIGlobalObject* aIncumbentGlobal)
+ : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal,
+ aAllocationSite, aIncumbentGlobal)),
+ mPropagateUserInputEventHandling(false) {
+ MOZ_ASSERT(js::IsFunctionObject(aCallback));
+
+ if (aPromise) {
+ JS::PromiseUserInputEventHandlingState state =
+ JS::GetPromiseUserInputEventHandlingState(aPromise);
+ mPropagateUserInputEventHandling =
+ state ==
+ JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
+ }
+ }
+
+ virtual ~PromiseJobRunnable() = default;
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT
+ virtual void Run(AutoSlowOperation& aAso) override {
+ JSObject* callback = mCallback->CallbackPreserveColor();
+ nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
+ if (global && !global->IsDying()) {
+ // Propagate the user input event handling bit if needed.
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+ RefPtr<Document> doc;
+ if (win) {
+ doc = win->GetExtantDoc();
+ }
+ AutoHandlingUserInputStatePusher userInpStatePusher(
+ mPropagateUserInputEventHandling);
+
+ mCallback->Call("promise callback");
+ aAso.CheckForInterrupt();
+ }
+ // Now that mCallback is no longer needed, clear any pointers it contains to
+ // JS GC things. This removes any storebuffer entries associated with those
+ // pointers, which can cause problems by taking up memory and by triggering
+ // minor GCs. This otherwise would not happen until the next minor GC or
+ // cycle collection.
+ mCallback->Reset();
+ }
+
+ virtual bool Suppressed() override {
+ JSObject* callback = mCallback->CallbackPreserveColor();
+ nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
+ return global && global->IsInSyncOperation();
+ }
+
+ private:
+ const RefPtr<PromiseJobCallback> mCallback;
+ bool mPropagateUserInputEventHandling;
+};
+
+JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) {
+ nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
+ if (global) {
+ return global->GetGlobalJSObject();
+ }
+ return nullptr;
+}
+
+bool CycleCollectedJSContext::enqueuePromiseJob(
+ JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob,
+ JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) {
+ MOZ_ASSERT(aCx == Context());
+ MOZ_ASSERT(Get() == this);
+
+ nsIGlobalObject* global = nullptr;
+ if (aIncumbentGlobal) {
+ global = xpc::NativeGlobal(aIncumbentGlobal);
+ }
+ JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
+ RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable(
+ aPromise, aJob, jobGlobal, aAllocationSite, global);
+ DispatchToMicroTask(runnable.forget());
+ return true;
+}
+
+// Used only by the SpiderMonkey Debugger API, and even then only via
+// JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is
+// not affected; see comments in js/public/Promise.h.
+void CycleCollectedJSContext::runJobs(JSContext* aCx) {
+ MOZ_ASSERT(aCx == Context());
+ MOZ_ASSERT(Get() == this);
+ PerformMicroTaskCheckPoint();
+}
+
+bool CycleCollectedJSContext::empty() const {
+ // This is our override of JS::JobQueue::empty. Since that interface is only
+ // concerned with the ordinary microtask queue, not the debugger microtask
+ // queue, we only report on the former.
+ return mPendingMicroTaskRunnables.empty();
+}
+
+// Preserve a debuggee's microtask queue while it is interrupted by the
+// debugger. See the comments for JS::AutoDebuggerJobQueueInterruption.
+class CycleCollectedJSContext::SavedMicroTaskQueue
+ : public JS::JobQueue::SavedJobQueue {
+ public:
+ explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) {
+ ccjs->mDebuggerRecursionDepth++;
+ ccjs->mPendingMicroTaskRunnables.swap(mQueue);
+ }
+
+ ~SavedMicroTaskQueue() {
+ // The JS Debugger attempts to maintain the invariant that microtasks which
+ // occur durring debugger operation are completely flushed from the task
+ // queue before returning control to the debuggee, in order to avoid
+ // micro-tasks generated during debugging from interfering with regular
+ // operation.
+ //
+ // While the vast majority of microtasks can be reliably flushed,
+ // synchronous operations (see nsAutoSyncOperation) such as printing and
+ // alert diaglogs suppress the execution of some microtasks.
+ //
+ // When PerformMicroTaskCheckpoint is run while microtasks are suppressed,
+ // any suppressed microtasks are gathered into a new SuppressedMicroTasks
+ // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a
+ // result, AutoDebuggerJobQueueInterruption::runJobs is not able to
+ // correctly guarantee that the microtask queue is totally empty in the
+ // presence of sync operations.
+ //
+ // Previous versions of this code release-asserted that the queue was empty,
+ // causing user observable crashes (Bug 1849675). To avoid this, we instead
+ // choose to move suspended microtasks from the SavedMicroTaskQueue to the
+ // main microtask queue in this destructor. This means that jobs enqueued
+ // during synchnronous events under debugger control may produce events
+ // which run outside the debugger, but this is viewed as strictly
+ // preferrable to crashing.
+ MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1);
+ MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth);
+ RefPtr<MicroTaskRunnable> maybeSuppressedTasks;
+
+ // Handle the case where there is a SuppressedMicroTask still in the queue.
+ if (!ccjs->mPendingMicroTaskRunnables.empty()) {
+ maybeSuppressedTasks = ccjs->mPendingMicroTaskRunnables.front();
+ ccjs->mPendingMicroTaskRunnables.pop_front();
+ }
+
+ MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty());
+ ccjs->mDebuggerRecursionDepth--;
+ ccjs->mPendingMicroTaskRunnables.swap(mQueue);
+
+ // Re-enqueue the suppressed task now that we've put the original microtask
+ // queue back.
+ if (maybeSuppressedTasks) {
+ ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks);
+ }
+ }
+
+ private:
+ CycleCollectedJSContext* ccjs;
+ std::deque<RefPtr<MicroTaskRunnable>> mQueue;
+};
+
+js::UniquePtr<JS::JobQueue::SavedJobQueue>
+CycleCollectedJSContext::saveJobQueue(JSContext* cx) {
+ auto saved = js::MakeUnique<SavedMicroTaskQueue>(this);
+ if (!saved) {
+ // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor
+ // is never called, so mPendingMicroTaskRunnables is still initialized.
+ JS_ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ return saved;
+}
+
+/* static */
+void CycleCollectedJSContext::PromiseRejectionTrackerCallback(
+ JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise,
+ JS::PromiseRejectionHandlingState state, void* aData) {
+ CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
+
+ MOZ_ASSERT(aCx == self->Context());
+ MOZ_ASSERT(Get() == self);
+
+ // TODO: Bug 1549351 - Promise rejection event should not be sent for
+ // cross-origin scripts
+
+ PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
+ PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
+ uint64_t promiseID = JS::GetPromiseID(aPromise);
+
+ if (state == JS::PromiseRejectionHandlingState::Unhandled) {
+ PromiseDebugging::AddUncaughtRejection(aPromise);
+ if (!aMutedErrors) {
+ RefPtr<Promise> promise =
+ Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
+ aboutToBeNotified.AppendElement(promise);
+ unhandled.InsertOrUpdate(promiseID, std::move(promise));
+ }
+ } else {
+ PromiseDebugging::AddConsumedRejection(aPromise);
+ for (size_t i = 0; i < aboutToBeNotified.Length(); i++) {
+ if (aboutToBeNotified[i] &&
+ aboutToBeNotified[i]->PromiseObj() == aPromise) {
+ // To avoid large amounts of memmoves, we don't shrink the vector
+ // here. Instead, we filter out nullptrs when iterating over the
+ // vector later.
+ aboutToBeNotified[i] = nullptr;
+ DebugOnly<bool> isFound = unhandled.Remove(promiseID);
+ MOZ_ASSERT(isFound);
+ return;
+ }
+ }
+ RefPtr<Promise> promise;
+ unhandled.Remove(promiseID, getter_AddRefs(promise));
+ if (!promise && !aMutedErrors) {
+ nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
+ if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) {
+ RootedDictionary<PromiseRejectionEventInit> init(aCx);
+ init.mPromise = Promise::CreateFromExisting(global, aPromise);
+ init.mReason = JS::GetPromiseResult(aPromise);
+
+ RefPtr<PromiseRejectionEvent> event =
+ PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns,
+ init);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(owner, event.forget());
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+ }
+}
+
+already_AddRefed<Exception> CycleCollectedJSContext::GetPendingException()
+ const {
+ MOZ_ASSERT(mJSContext);
+
+ nsCOMPtr<Exception> out = mPendingException;
+ return out.forget();
+}
+
+void CycleCollectedJSContext::SetPendingException(Exception* aException) {
+ MOZ_ASSERT(mJSContext);
+ mPendingException = aException;
+}
+
+std::deque<RefPtr<MicroTaskRunnable>>&
+CycleCollectedJSContext::GetMicroTaskQueue() {
+ MOZ_ASSERT(mJSContext);
+ return mPendingMicroTaskRunnables;
+}
+
+std::deque<RefPtr<MicroTaskRunnable>>&
+CycleCollectedJSContext::GetDebuggerMicroTaskQueue() {
+ MOZ_ASSERT(mJSContext);
+ return mDebuggerMicroTaskQueue;
+}
+
+void CycleCollectedJSContext::ProcessStableStateQueue() {
+ MOZ_ASSERT(mJSContext);
+ MOZ_RELEASE_ASSERT(!mDoingStableStates);
+ mDoingStableStates = true;
+
+ // When run, one event can add another event to the mStableStateEvents, as
+ // such you can't use iterators here.
+ for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) {
+ nsCOMPtr<nsIRunnable> event = std::move(mStableStateEvents[i]);
+ event->Run();
+ }
+
+ mStableStateEvents.Clear();
+ mDoingStableStates = false;
+}
+
+void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) {
+ MOZ_ASSERT(mJSContext);
+ MOZ_RELEASE_ASSERT(!mDoingStableStates);
+ mDoingStableStates = true;
+
+ nsTArray<PendingIDBTransactionData> localQueue =
+ std::move(mPendingIDBTransactions);
+
+ localQueue.RemoveLastElements(
+ localQueue.end() -
+ std::remove_if(localQueue.begin(), localQueue.end(),
+ [aRecursionDepth](PendingIDBTransactionData& data) {
+ if (data.mRecursionDepth != aRecursionDepth) {
+ return false;
+ }
+
+ {
+ nsCOMPtr<nsIRunnable> transaction =
+ std::move(data.mTransaction);
+ transaction->Run();
+ }
+
+ return true;
+ }));
+
+ // If mPendingIDBTransactions has events in it now, they were added from
+ // something we called, so they belong at the end of the queue.
+ localQueue.AppendElements(std::move(mPendingIDBTransactions));
+ mPendingIDBTransactions = std::move(localQueue);
+ mDoingStableStates = false;
+}
+
+void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) {
+ // If ProcessNextEvent was called during a microtask callback, we
+ // must process any pending microtasks before blocking in the event loop,
+ // otherwise we may deadlock until an event enters the queue later.
+ if (aMightBlock && PerformMicroTaskCheckPoint()) {
+ // If any microtask was processed, we post a dummy event in order to
+ // force the ProcessNextEvent call not to block. This is required
+ // to support nested event loops implemented using a pattern like
+ // "while (condition) thread.processNextEvent(true)", in case the
+ // condition is triggered here by a Promise "then" callback.
+ NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
+ }
+}
+
+void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) {
+ MOZ_ASSERT(mJSContext);
+
+ // See HTML 6.1.4.2 Processing model
+
+ // Step 4.1: Execute microtasks.
+ PerformMicroTaskCheckPoint();
+
+ // Step 4.2 Execute any events that were waiting for a stable state.
+ ProcessStableStateQueue();
+
+ // This should be a fast test so that it won't affect the next task
+ // processing.
+ MaybePokeGC();
+}
+
+void CycleCollectedJSContext::AfterProcessMicrotasks() {
+ MOZ_ASSERT(mJSContext);
+ // Notify unhandled promise rejections:
+ // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
+ if (mAboutToBeNotifiedRejectedPromises.Length()) {
+ RefPtr<NotifyUnhandledRejections> runnable = new NotifyUnhandledRejections(
+ std::move(mAboutToBeNotifiedRejectedPromises));
+ NS_DispatchToCurrentThread(runnable);
+ }
+ // Cleanup Indexed Database transactions:
+ // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
+ CleanupIDBTransactions(RecursionDepth());
+
+ // Clear kept alive objects in JS WeakRef.
+ // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint
+ //
+ // ECMAScript implementations are expected to call ClearKeptObjects when a
+ // synchronous sequence of ECMAScript execution completes.
+ //
+ // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
+ JS::ClearKeptObjects(mJSContext);
+}
+
+void CycleCollectedJSContext::MaybePokeGC() {
+ // Worker-compatible check to see if we want to do an idle-time minor
+ // GC.
+ class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable {
+ public:
+ using mozilla::IdleRunnable::IdleRunnable;
+
+ public:
+ IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {}
+
+ NS_IMETHOD Run() override {
+ CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
+ if (ccrt) {
+ ccrt->RunIdleTimeGCTask();
+ }
+ return NS_OK;
+ }
+ };
+
+ if (Runtime()->IsIdleGCTaskNeeded()) {
+ nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
+ NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle);
+ Runtime()->SetPendingIdleGCTask();
+ }
+}
+
+uint32_t CycleCollectedJSContext::RecursionDepth() const {
+ // Debugger interruptions are included in the recursion depth so that debugger
+ // microtask checkpoints do not run IDB transactions which were initiated
+ // before the interruption.
+ return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth;
+}
+
+void CycleCollectedJSContext::RunInStableState(
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_ASSERT(mJSContext);
+ mStableStateEvents.AppendElement(std::move(aRunnable));
+}
+
+void CycleCollectedJSContext::AddPendingIDBTransaction(
+ already_AddRefed<nsIRunnable>&& aTransaction) {
+ MOZ_ASSERT(mJSContext);
+
+ PendingIDBTransactionData data;
+ data.mTransaction = aTransaction;
+
+ MOZ_ASSERT(mOwningThread);
+ data.mRecursionDepth = RecursionDepth();
+
+ // There must be an event running to get here.
+#ifndef MOZ_WIDGET_COCOA
+ MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth);
+#else
+ // XXX bug 1261143
+ // Recursion depth should be greater than mBaseRecursionDepth,
+ // or the runnable will stay in the queue forever.
+ if (data.mRecursionDepth <= mBaseRecursionDepth) {
+ data.mRecursionDepth = mBaseRecursionDepth + 1;
+ }
+#endif
+
+ mPendingIDBTransactions.AppendElement(std::move(data));
+}
+
+void CycleCollectedJSContext::DispatchToMicroTask(
+ already_AddRefed<MicroTaskRunnable> aRunnable) {
+ RefPtr<MicroTaskRunnable> runnable(aRunnable);
+
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(runnable);
+
+ JS::JobQueueMayNotBeEmpty(Context());
+
+ LogMicroTaskRunnable::LogDispatch(runnable.get());
+ mPendingMicroTaskRunnables.push_back(std::move(runnable));
+}
+
+class AsyncMutationHandler final : public mozilla::Runnable {
+ public:
+ AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {}
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->PerformMicroTaskCheckPoint();
+ }
+ return NS_OK;
+ }
+};
+
+SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext)
+ : mContext(aContext),
+ mSuppressionGeneration(aContext->mSuppressionGeneration) {}
+
+bool SuppressedMicroTasks::Suppressed() {
+ if (mSuppressionGeneration == mContext->mSuppressionGeneration) {
+ return true;
+ }
+
+ for (std::deque<RefPtr<MicroTaskRunnable>>::reverse_iterator it =
+ mSuppressedMicroTaskRunnables.rbegin();
+ it != mSuppressedMicroTaskRunnables.rend(); ++it) {
+ mContext->GetMicroTaskQueue().push_front(*it);
+ }
+ mContext->mSuppressedMicroTasks = nullptr;
+
+ return false;
+}
+
+bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
+ if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
+ AfterProcessMicrotasks();
+ // Nothing to do, return early.
+ return false;
+ }
+
+ uint32_t currentDepth = RecursionDepth();
+ if (mMicroTaskRecursionDepth >= currentDepth && !aForce) {
+ // We are already executing microtasks for the current recursion depth.
+ return false;
+ }
+
+ if (mTargetedMicroTaskRecursionDepth != 0 &&
+ mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth !=
+ currentDepth) {
+ return false;
+ }
+
+ if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
+ // Special case for main thread where DOM mutations may happen when
+ // it is not safe to run scripts.
+ nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
+ return false;
+ }
+
+ mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
+ MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0);
+ mMicroTaskRecursionDepth = currentDepth;
+
+ AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS);
+
+ bool didProcess = false;
+ AutoSlowOperation aso;
+
+ for (;;) {
+ RefPtr<MicroTaskRunnable> runnable;
+ if (!mDebuggerMicroTaskQueue.empty()) {
+ runnable = std::move(mDebuggerMicroTaskQueue.front());
+ mDebuggerMicroTaskQueue.pop_front();
+ } else if (!mPendingMicroTaskRunnables.empty()) {
+ runnable = std::move(mPendingMicroTaskRunnables.front());
+ mPendingMicroTaskRunnables.pop_front();
+ } else {
+ break;
+ }
+
+ if (runnable->Suppressed()) {
+ // Microtasks in worker shall never be suppressed.
+ // Otherwise, mPendingMicroTaskRunnables will be replaced later with
+ // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
+ MOZ_ASSERT(NS_IsMainThread());
+ JS::JobQueueMayNotBeEmpty(Context());
+ if (runnable != mSuppressedMicroTasks) {
+ if (!mSuppressedMicroTasks) {
+ mSuppressedMicroTasks = new SuppressedMicroTasks(this);
+ }
+ mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back(
+ runnable);
+ }
+ } else {
+ if (mPendingMicroTaskRunnables.empty() &&
+ mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) {
+ JS::JobQueueIsEmpty(Context());
+ }
+ didProcess = true;
+
+ LogMicroTaskRunnable::Run log(runnable.get());
+ runnable->Run(aso);
+ runnable = nullptr;
+ }
+ }
+
+ // Put back the suppressed microtasks so that they will be run later.
+ // Note, it is possible that we end up keeping these suppressed tasks around
+ // for some time, but no longer than spinning the event loop nestedly
+ // (sync XHR, alert, etc.)
+ if (mSuppressedMicroTasks) {
+ mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks);
+ }
+
+ AfterProcessMicrotasks();
+
+ return didProcess;
+}
+
+void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() {
+ // Don't do normal microtask handling checks here, since whoever is calling
+ // this method is supposed to know what they are doing.
+
+ AutoSlowOperation aso;
+ for (;;) {
+ // For a debugger microtask checkpoint, we always use the debugger microtask
+ // queue.
+ std::deque<RefPtr<MicroTaskRunnable>>* microtaskQueue =
+ &GetDebuggerMicroTaskQueue();
+
+ if (microtaskQueue->empty()) {
+ break;
+ }
+
+ RefPtr<MicroTaskRunnable> runnable = std::move(microtaskQueue->front());
+ MOZ_ASSERT(runnable);
+
+ LogMicroTaskRunnable::Run log(runnable.get());
+
+ // This function can re-enter, so we remove the element before calling.
+ microtaskQueue->pop_front();
+
+ if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
+ JS::JobQueueIsEmpty(Context());
+ }
+ runnable->Run(aso);
+ runnable = nullptr;
+ }
+
+ AfterProcessMicrotasks();
+}
+
+NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() {
+ for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
+ CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get();
+ NS_ENSURE_STATE(cccx);
+
+ RefPtr<Promise>& promise = mUnhandledRejections[i];
+ if (!promise) {
+ continue;
+ }
+
+ JS::RootingContext* cx = cccx->RootingCx();
+ JS::RootedObject promiseObj(cx, promise->PromiseObj());
+ MOZ_ASSERT(JS::IsPromiseObject(promiseObj));
+
+ // Only fire unhandledrejection if the promise is still not handled;
+ uint64_t promiseID = JS::GetPromiseID(promiseObj);
+ if (!JS::GetPromiseIsHandled(promiseObj)) {
+ if (nsCOMPtr<EventTarget> target =
+ do_QueryInterface(promise->GetParentObject())) {
+ RootedDictionary<PromiseRejectionEventInit> init(cx);
+ init.mPromise = promise;
+ init.mReason = JS::GetPromiseResult(promiseObj);
+ init.mCancelable = true;
+
+ RefPtr<PromiseRejectionEvent> event =
+ PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns,
+ init);
+ // We don't use the result of dispatching event here to check whether to
+ // report the Promise to console.
+ target->DispatchEvent(*event);
+ }
+ }
+
+ cccx = CycleCollectedJSContext::Get();
+ NS_ENSURE_STATE(cccx);
+ if (!JS::GetPromiseIsHandled(promiseObj)) {
+ DebugOnly<bool> isFound =
+ cccx->mPendingUnhandledRejections.Remove(promiseID);
+ MOZ_ASSERT(isFound);
+ }
+
+ // If a rejected promise is being handled in "unhandledrejection" event
+ // handler, it should be removed from the table in
+ // PromiseRejectionTrackerCallback.
+ MOZ_ASSERT(!cccx->mPendingUnhandledRejections.Lookup(promiseID));
+ }
+ return NS_OK;
+}
+
+nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() {
+ CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get();
+ NS_ENSURE_STATE(cccx);
+
+ for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
+ RefPtr<Promise>& promise = mUnhandledRejections[i];
+ if (!promise) {
+ continue;
+ }
+
+ JS::RootedObject promiseObj(cccx->RootingCx(), promise->PromiseObj());
+ cccx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
+ }
+ return NS_OK;
+}
+
+class FinalizationRegistryCleanup::CleanupRunnable
+ : public DiscardableRunnable {
+ public:
+ explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork)
+ : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {}
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override {
+ mCleanupWork->DoCleanup();
+ return NS_OK;
+ }
+
+ private:
+ FinalizationRegistryCleanup* mCleanupWork;
+};
+
+FinalizationRegistryCleanup::FinalizationRegistryCleanup(
+ CycleCollectedJSContext* aContext)
+ : mContext(aContext) {}
+
+void FinalizationRegistryCleanup::Destroy() {
+ // This must happen before the CycleCollectedJSContext destructor calls
+ // JS_DestroyContext().
+ mCallbacks.reset();
+}
+
+void FinalizationRegistryCleanup::Init() {
+ JSContext* cx = mContext->Context();
+ mCallbacks.init(cx);
+ JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this);
+}
+
+/* static */
+void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
+ JSObject* aIncumbentGlobal,
+ void* aData) {
+ FinalizationRegistryCleanup* cleanup =
+ static_cast<FinalizationRegistryCleanup*>(aData);
+ cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal);
+}
+
+void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup,
+ JSObject* aIncumbentGlobal) {
+ bool firstCallback = mCallbacks.empty();
+
+ MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal}));
+
+ if (firstCallback) {
+ RefPtr<CleanupRunnable> cleanup = new CleanupRunnable(this);
+ NS_DispatchToCurrentThread(cleanup.forget());
+ }
+}
+
+void FinalizationRegistryCleanup::DoCleanup() {
+ if (mCallbacks.empty()) {
+ return;
+ }
+
+ JS::RootingContext* cx = mContext->RootingCx();
+
+ JS::Rooted<CallbackVector> callbacks(cx);
+ std::swap(callbacks.get(), mCallbacks.get());
+
+ for (const Callback& callback : callbacks) {
+ JS::ExposeObjectToActiveJS(
+ JS_GetFunctionObject(callback.mCallbackFunction));
+ JS::ExposeObjectToActiveJS(callback.mIncumbentGlobal);
+
+ JS::RootedObject functionObj(
+ cx, JS_GetFunctionObject(callback.mCallbackFunction));
+ JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj));
+
+ nsIGlobalObject* incumbentGlobal =
+ xpc::NativeGlobal(callback.mIncumbentGlobal);
+ if (!incumbentGlobal) {
+ continue;
+ }
+
+ RefPtr<FinalizationRegistryCleanupCallback> cleanupCallback(
+ new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr,
+ incumbentGlobal));
+
+ nsIGlobalObject* global =
+ xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor());
+ if (global) {
+ cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup");
+ }
+ }
+}
+
+void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) {
+ JS::TraceRoot(trc, &mCallbackFunction, "mCallbackFunction");
+ JS::TraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal");
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h
new file mode 100644
index 0000000000..bbe47a57a5
--- /dev/null
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -0,0 +1,396 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CycleCollectedJSContext_h
+#define mozilla_CycleCollectedJSContext_h
+
+#include <deque>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/AtomList.h"
+#include "mozilla/dom/Promise.h"
+#include "js/GCVector.h"
+#include "js/Promise.h"
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+
+class nsCycleCollectionNoteRootCallback;
+class nsIRunnable;
+class nsThread;
+
+namespace mozilla {
+class AutoSlowOperation;
+
+class CycleCollectedJSContext;
+class CycleCollectedJSRuntime;
+
+namespace dom {
+class Exception;
+class WorkerJSContext;
+class WorkletJSContext;
+} // namespace dom
+
+// Contains various stats about the cycle collection.
+struct CycleCollectorResults {
+ CycleCollectorResults() {
+ // Initialize here so when we increment mNumSlices the first time we're
+ // not using uninitialized memory.
+ Init();
+ }
+
+ void Init() {
+ mForcedGC = false;
+ mSuspectedAtCCStart = 0;
+ mMergedZones = false;
+ mAnyManual = false;
+ mVisitedRefCounted = 0;
+ mVisitedGCed = 0;
+ mFreedRefCounted = 0;
+ mFreedGCed = 0;
+ mFreedJSZones = 0;
+ mNumSlices = 1;
+ // mNumSlices is initialized to one, because we call Init() after the
+ // per-slice increment of mNumSlices has already occurred.
+ }
+
+ bool mForcedGC;
+ bool mMergedZones;
+ // mAnyManual is true if any slice was manually triggered, and at shutdown.
+ bool mAnyManual;
+ uint32_t mSuspectedAtCCStart;
+ uint32_t mVisitedRefCounted;
+ uint32_t mVisitedGCed;
+ uint32_t mFreedRefCounted;
+ uint32_t mFreedGCed;
+ uint32_t mFreedJSZones;
+ uint32_t mNumSlices;
+};
+
+class MicroTaskRunnable {
+ public:
+ MicroTaskRunnable() = default;
+ NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable)
+ MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0;
+ virtual bool Suppressed() { return false; }
+
+ protected:
+ virtual ~MicroTaskRunnable() = default;
+};
+
+// Store the suppressed mictotasks in another microtask so that operations
+// for the microtask queue as a whole keep working.
+class SuppressedMicroTasks : public MicroTaskRunnable {
+ public:
+ explicit SuppressedMicroTasks(CycleCollectedJSContext* aContext);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {}
+ virtual bool Suppressed();
+
+ CycleCollectedJSContext* mContext;
+ uint64_t mSuppressionGeneration;
+ std::deque<RefPtr<MicroTaskRunnable>> mSuppressedMicroTaskRunnables;
+};
+
+// Support for JS FinalizationRegistry objects, which allow a JS callback to be
+// registered that is called when objects die.
+//
+// We keep a vector of functions that call back into the JS engine along
+// with their associated incumbent globals, one per FinalizationRegistry object
+// that has pending cleanup work. These are run in their own task.
+class FinalizationRegistryCleanup {
+ public:
+ explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext);
+ void Init();
+ void Destroy();
+ void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal);
+ MOZ_CAN_RUN_SCRIPT void DoCleanup();
+
+ private:
+ static void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal,
+ void* aData);
+
+ class CleanupRunnable;
+
+ struct Callback {
+ JSFunction* mCallbackFunction;
+ JSObject* mIncumbentGlobal;
+ void trace(JSTracer* trc);
+ };
+
+ // This object is part of CycleCollectedJSContext, so it's safe to have a raw
+ // pointer to its containing context here.
+ CycleCollectedJSContext* mContext;
+
+ using CallbackVector = JS::GCVector<Callback, 0, InfallibleAllocPolicy>;
+ JS::PersistentRooted<CallbackVector> mCallbacks;
+};
+
+class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue {
+ friend class CycleCollectedJSRuntime;
+ friend class SuppressedMicroTasks;
+
+ protected:
+ CycleCollectedJSContext();
+ virtual ~CycleCollectedJSContext();
+
+ MOZ_IS_CLASS_INIT
+ nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes);
+
+ virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ static void PromiseRejectionTrackerCallback(
+ JSContext* aCx, bool aMutedErrors, JS::Handle<JSObject*> aPromise,
+ JS::PromiseRejectionHandlingState state, void* aData);
+
+ void AfterProcessMicrotasks();
+
+ public:
+ void ProcessStableStateQueue();
+
+ private:
+ void CleanupIDBTransactions(uint32_t aRecursionDepth);
+
+ public:
+ virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; }
+ virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; }
+
+ CycleCollectedJSRuntime* Runtime() const {
+ MOZ_ASSERT(mRuntime);
+ return mRuntime;
+ }
+
+ already_AddRefed<dom::Exception> GetPendingException() const;
+ void SetPendingException(dom::Exception* aException);
+
+ std::deque<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
+ std::deque<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
+
+ JSContext* Context() const {
+ MOZ_ASSERT(mJSContext);
+ return mJSContext;
+ }
+
+ JS::RootingContext* RootingCx() const {
+ MOZ_ASSERT(mJSContext);
+ return JS::RootingContext::get(mJSContext);
+ }
+
+ void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) {
+ mTargetedMicroTaskRecursionDepth = aDepth;
+ }
+
+ void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; }
+
+ protected:
+ JSContext* MaybeContext() const { return mJSContext; }
+
+ public:
+ // nsThread entrypoints
+ //
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
+ // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
+ // But we really should!
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void BeforeProcessTask(bool aMightBlock);
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate
+ // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now.
+ // But we really should!
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void AfterProcessTask(uint32_t aRecursionDepth);
+
+ // Check whether any eager thresholds have been reached, which would mean
+ // an idle GC task (minor or major) would be useful.
+ virtual void MaybePokeGC();
+
+ uint32_t RecursionDepth() const;
+
+ // Run in stable state (call through nsContentUtils)
+ void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
+
+ // Get the CycleCollectedJSContext for a JSContext.
+ // Returns null only if Initialize() has not completed on or during
+ // destruction of the CycleCollectedJSContext.
+ static CycleCollectedJSContext* GetFor(JSContext* aCx);
+
+ // Get the current thread's CycleCollectedJSContext. Returns null if there
+ // isn't one.
+ static CycleCollectedJSContext* Get();
+
+ // Queue an async microtask to the current main or worker thread.
+ virtual void DispatchToMicroTask(
+ already_AddRefed<MicroTaskRunnable> aRunnable);
+
+ // Call EnterMicroTask when you're entering JS execution.
+ // Usually the best way to do this is to use nsAutoMicroTask.
+ void EnterMicroTask() { ++mMicroTaskLevel; }
+
+ MOZ_CAN_RUN_SCRIPT
+ void LeaveMicroTask() {
+ if (--mMicroTaskLevel == 0) {
+ PerformMicroTaskCheckPoint();
+ }
+ }
+
+ uint32_t MicroTaskLevel() const { return mMicroTaskLevel; }
+
+ void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; }
+
+ MOZ_CAN_RUN_SCRIPT
+ bool PerformMicroTaskCheckPoint(bool aForce = false);
+
+ MOZ_CAN_RUN_SCRIPT
+ void PerformDebuggerMicroTaskCheckpoint();
+
+ bool IsInStableOrMetaStableState() const { return mDoingStableStates; }
+
+ // Storage for watching rejected promises waiting for some client to
+ // consume their rejection.
+ // Promises in this list have been rejected in the last turn of the
+ // event loop without the rejection being handled.
+ // Note that this can contain nullptrs in place of promises removed because
+ // they're consumed before it'd be reported.
+ JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
+ mUncaughtRejections;
+
+ // Promises in this list have previously been reported as rejected
+ // (because they were in the above list), but the rejection was handled
+ // in the last turn of the event loop.
+ JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>>
+ mConsumedRejections;
+ nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */>>
+ mUncaughtRejectionObservers;
+
+ virtual bool IsSystemCaller() const = 0;
+
+ // Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads.
+ virtual void ReportError(JSErrorReport* aReport,
+ JS::ConstUTF8CharsZ aToStringResult) {
+ MOZ_ASSERT_UNREACHABLE("Not supported");
+ }
+
+ private:
+ // JS::JobQueue implementation: see js/public/Promise.h.
+ // SpiderMonkey uses some of these methods to enqueue promise resolution jobs.
+ // Others protect the debuggee microtask queue from the debugger's
+ // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for
+ // details.
+ JSObject* getIncumbentGlobal(JSContext* cx) override;
+ bool enqueuePromiseJob(JSContext* cx, JS::Handle<JSObject*> promise,
+ JS::Handle<JSObject*> job,
+ JS::Handle<JSObject*> allocationSite,
+ JS::Handle<JSObject*> incumbentGlobal) override;
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey
+ // headers. The caller presumably knows this can run script (like everything
+ // in SpiderMonkey!) and will deal.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void runJobs(JSContext* cx) override;
+ bool empty() const override;
+ class SavedMicroTaskQueue;
+ js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) override;
+
+ private:
+ CycleCollectedJSRuntime* mRuntime;
+
+ JSContext* mJSContext;
+
+ nsCOMPtr<dom::Exception> mPendingException;
+ nsThread* mOwningThread; // Manual refcounting to avoid include hell.
+
+ struct PendingIDBTransactionData {
+ nsCOMPtr<nsIRunnable> mTransaction;
+ uint32_t mRecursionDepth;
+ };
+
+ nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
+ nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
+ uint32_t mBaseRecursionDepth;
+ bool mDoingStableStates;
+
+ // If set to none 0, microtasks will be processed only when recursion depth
+ // is the set value.
+ uint32_t mTargetedMicroTaskRecursionDepth;
+
+ uint32_t mMicroTaskLevel;
+
+ std::deque<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
+ std::deque<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
+ RefPtr<SuppressedMicroTasks> mSuppressedMicroTasks;
+ uint64_t mSuppressionGeneration;
+
+ // How many times the debugger has interrupted execution, possibly creating
+ // microtask checkpoints in places that they would not normally occur.
+ uint32_t mDebuggerRecursionDepth;
+
+ uint32_t mMicroTaskRecursionDepth;
+
+ // This implements about-to-be-notified rejected promises list in the spec.
+ // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
+ typedef nsTArray<RefPtr<dom::Promise>> PromiseArray;
+ PromiseArray mAboutToBeNotifiedRejectedPromises;
+
+ // This is for the "outstanding rejected promises weak set" in the spec,
+ // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
+ // We use different data structure and opposite logic here to achieve the same
+ // effect. Basically this is used for tracking the rejected promise that does
+ // NOT need firing a rejectionhandled event. We will check the table to see if
+ // firing rejectionhandled event is required when a rejected promise is being
+ // handled.
+ //
+ // The rejected promise will be stored in the table if
+ // - it is unhandled, and
+ // - The unhandledrejection is not yet fired.
+ //
+ // And be removed when
+ // - it is handled, or
+ // - A unhandledrejection is fired and it isn't being handled in event
+ // handler.
+ typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable;
+ PromiseHashtable mPendingUnhandledRejections;
+
+ class NotifyUnhandledRejections final : public CancelableRunnable {
+ public:
+ explicit NotifyUnhandledRejections(PromiseArray&& aPromises)
+ : CancelableRunnable("NotifyUnhandledRejections"),
+ mUnhandledRejections(std::move(aPromises)) {}
+
+ NS_IMETHOD Run() final;
+
+ nsresult Cancel() final;
+
+ private:
+ PromiseArray mUnhandledRejections;
+ };
+
+ FinalizationRegistryCleanup mFinalizationRegistryCleanup;
+};
+
+class MOZ_STACK_CLASS nsAutoMicroTask {
+ public:
+ nsAutoMicroTask() {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->EnterMicroTask();
+ }
+ }
+ MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() {
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->LeaveMicroTask();
+ }
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CycleCollectedJSContext_h
diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp
new file mode 100644
index 0000000000..03af9e3074
--- /dev/null
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -0,0 +1,2027 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// We're dividing JS objects into 3 categories:
+//
+// 1. "real" roots, held by the JS engine itself or rooted through the root
+// and lock JS APIs. Roots from this category are considered black in the
+// cycle collector, any cycle they participate in is uncollectable.
+//
+// 2. certain roots held by C++ objects that are guaranteed to be alive.
+// Roots from this category are considered black in the cycle collector,
+// and any cycle they participate in is uncollectable. These roots are
+// traced from TraceNativeBlackRoots.
+//
+// 3. all other roots held by C++ objects that participate in cycle collection,
+// held by us (see TraceNativeGrayRoots). Roots from this category are
+// considered grey in the cycle collector; whether or not they are collected
+// depends on the objects that hold them.
+//
+// Note that if a root is in multiple categories the fact that it is in
+// category 1 or 2 that takes precedence, so it will be considered black.
+//
+// During garbage collection we switch to an additional mark color (gray) when
+// tracing inside TraceNativeGrayRoots. This allows us to walk those roots later
+// on and add all objects reachable only from them to the cycle collector.
+//
+// Phases:
+//
+// 1. marking of the roots in category 1 by having the JS GC do its marking
+// 2. marking of the roots in category 2 by having the JS GC call us back
+// (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots
+// 3. marking of the roots in category 3 by
+// TraceNativeGrayRootsInCollectingZones using an additional color (gray).
+// 4. end of GC, GC can sweep its heap
+//
+// At some later point, when the cycle collector runs:
+//
+// 5. walk gray objects and add them to the cycle collector, cycle collect
+//
+// JS objects that are part of cycles the cycle collector breaks will be
+// collected by the next JS GC.
+//
+// If WantAllTraces() is false the cycle collector will not traverse roots
+// from category 1 or any JS objects held by them. Any JS objects they hold
+// will already be marked by the JS GC and will thus be colored black
+// themselves. Any C++ objects they hold will have a missing (untraversed)
+// edge from the JS object to the C++ object and so it will be marked black
+// too. This decreases the number of objects that the cycle collector has to
+// deal with.
+// To improve debugging, if WantAllTraces() is true all JS objects are
+// traversed.
+
+#include "mozilla/CycleCollectedJSRuntime.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "js/Debug.h"
+#include "js/RealmOptions.h"
+#include "js/friend/DumpFunctions.h" // js::DumpHeap
+#include "js/GCAPI.h"
+#include "js/HeapAPI.h"
+#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/Warnings.h" // JS::SetWarningReporter
+#include "js/ShadowRealmCallbacks.h"
+#include "js/SliceBudget.h"
+#include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebuggerOnGCRunnable.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/PromiseDebugging.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ShadowRealmGlobalScope.h"
+#include "mozilla/dom/RegisterShadowRealmBindings.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCycleCollector.h"
+#include "nsDOMJSUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsJSUtils.h"
+#include "nsStringBuffer.h"
+#include "nsWrapperCache.h"
+#include "prenv.h"
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+#include "nsThread.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
+
+#ifdef NIGHTLY_BUILD
+// For performance reasons, we make the JS Dev Error Interceptor a Nightly-only
+// feature.
+# define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
+#endif // NIGHTLY_BUILD
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+struct DeferredFinalizeFunctionHolder {
+ DeferredFinalizeFunction run;
+ void* data;
+};
+
+class IncrementalFinalizeRunnable : public DiscardableRunnable {
+ typedef AutoTArray<DeferredFinalizeFunctionHolder, 16> DeferredFinalizeArray;
+ typedef CycleCollectedJSRuntime::DeferredFinalizerTable
+ DeferredFinalizerTable;
+
+ CycleCollectedJSRuntime* mRuntime;
+ DeferredFinalizeArray mDeferredFinalizeFunctions;
+ uint32_t mFinalizeFunctionToRun;
+ bool mReleasing;
+
+ static const PRTime SliceMillis = 5; /* ms */
+
+ public:
+ IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt,
+ DeferredFinalizerTable& aFinalizerTable);
+ virtual ~IncrementalFinalizeRunnable();
+
+ void ReleaseNow(bool aLimited);
+
+ NS_DECL_NSIRUNNABLE
+};
+
+} // namespace mozilla
+
+struct NoteWeakMapChildrenTracer : public JS::CallbackTracer {
+ NoteWeakMapChildrenTracer(JSRuntime* aRt,
+ nsCycleCollectionNoteRootCallback& aCb)
+ : JS::CallbackTracer(aRt, JS::TracerKind::Callback),
+ mCb(aCb),
+ mTracedAny(false),
+ mMap(nullptr),
+ mKey(nullptr),
+ mKeyDelegate(nullptr) {}
+ void onChild(JS::GCCellPtr aThing, const char* name) override;
+ nsCycleCollectionNoteRootCallback& mCb;
+ bool mTracedAny;
+ JSObject* mMap;
+ JS::GCCellPtr mKey;
+ JSObject* mKeyDelegate;
+};
+
+void NoteWeakMapChildrenTracer::onChild(JS::GCCellPtr aThing,
+ const char* name) {
+ if (aThing.is<JSString>()) {
+ return;
+ }
+
+ if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
+ return;
+ }
+
+ if (JS::IsCCTraceKind(aThing.kind())) {
+ mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
+ mTracedAny = true;
+ } else {
+ JS::TraceChildren(this, aThing);
+ }
+}
+
+struct NoteWeakMapsTracer : public js::WeakMapTracer {
+ NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
+ : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb) {}
+ void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override;
+ nsCycleCollectionNoteRootCallback& mCb;
+ NoteWeakMapChildrenTracer mChildTracer;
+};
+
+void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey,
+ JS::GCCellPtr aValue) {
+ // If nothing that could be held alive by this entry is marked gray, return.
+ if ((!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
+ MOZ_LIKELY(!mCb.WantAllTraces())) {
+ if (!aValue || !JS::GCThingIsMarkedGrayInCC(aValue) ||
+ aValue.is<JSString>()) {
+ return;
+ }
+ }
+
+ // The cycle collector can only properly reason about weak maps if it can
+ // reason about the liveness of their keys, which in turn requires that
+ // the key can be represented in the cycle collector graph. All existing
+ // uses of weak maps use either objects or scripts as keys, which are okay.
+ MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind()));
+
+ // As an emergency fallback for non-debug builds, if the key is not
+ // representable in the cycle collector graph, we treat it as marked. This
+ // can cause leaks, but is preferable to ignoring the binding, which could
+ // cause the cycle collector to free live objects.
+ if (!JS::IsCCTraceKind(aKey.kind())) {
+ aKey = nullptr;
+ }
+
+ JSObject* kdelegate = nullptr;
+ if (aKey.is<JSObject>()) {
+ kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
+ }
+
+ if (JS::IsCCTraceKind(aValue.kind())) {
+ mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue);
+ } else {
+ mChildTracer.mTracedAny = false;
+ mChildTracer.mMap = aMap;
+ mChildTracer.mKey = aKey;
+ mChildTracer.mKeyDelegate = kdelegate;
+
+ if (!aValue.is<JSString>()) {
+ JS::TraceChildren(&mChildTracer, aValue);
+ }
+
+ // The delegate could hold alive the key, so report something to the CC
+ // if we haven't already.
+ if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGrayInCC(aKey) &&
+ kdelegate) {
+ mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr);
+ }
+ }
+}
+
+// Report whether the key or value of a weak mapping entry are gray but need to
+// be marked black.
+static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey,
+ JS::GCCellPtr aValue,
+ bool* aKeyShouldBeBlack,
+ bool* aValueShouldBeBlack) {
+ *aKeyShouldBeBlack = false;
+ *aValueShouldBeBlack = false;
+
+ // If nothing that could be held alive by this entry is marked gray, return.
+ bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey);
+ bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
+ aValue.kind() != JS::TraceKind::String;
+ if (!keyMightNeedMarking && !valueMightNeedMarking) {
+ return;
+ }
+
+ if (!JS::IsCCTraceKind(aKey.kind())) {
+ aKey = nullptr;
+ }
+
+ if (keyMightNeedMarking && aKey.is<JSObject>()) {
+ JSObject* kdelegate =
+ js::UncheckedUnwrapWithoutExpose(&aKey.as<JSObject>());
+ if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) &&
+ (!aMap || !JS::ObjectIsMarkedGray(aMap))) {
+ *aKeyShouldBeBlack = true;
+ }
+ }
+
+ if (aValue && JS::GCThingIsMarkedGrayInCC(aValue) &&
+ (!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) &&
+ (!aMap || !JS::ObjectIsMarkedGray(aMap)) &&
+ aValue.kind() != JS::TraceKind::Shape) {
+ *aValueShouldBeBlack = true;
+ }
+}
+
+struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer {
+ explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt)
+ : js::WeakMapTracer(aRt) {}
+
+ void FixAll() {
+ do {
+ mAnyMarked = false;
+ js::TraceWeakMaps(this);
+ } while (mAnyMarked);
+ }
+
+ void trace(JSObject* aMap, JS::GCCellPtr aKey,
+ JS::GCCellPtr aValue) override {
+ bool keyShouldBeBlack;
+ bool valueShouldBeBlack;
+ ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
+ &valueShouldBeBlack);
+ if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) {
+ mAnyMarked = true;
+ }
+
+ if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) {
+ mAnyMarked = true;
+ }
+ }
+
+ MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked;
+};
+
+#ifdef DEBUG
+// Check whether weak maps are marked correctly according to the logic above.
+struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer {
+ explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt)
+ : js::WeakMapTracer(aRt), mFailed(false) {}
+
+ static bool Check(JSRuntime* aRt) {
+ CheckWeakMappingGrayBitsTracer tracer(aRt);
+ js::TraceWeakMaps(&tracer);
+ return !tracer.mFailed;
+ }
+
+ void trace(JSObject* aMap, JS::GCCellPtr aKey,
+ JS::GCCellPtr aValue) override {
+ bool keyShouldBeBlack;
+ bool valueShouldBeBlack;
+ ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack,
+ &valueShouldBeBlack);
+
+ if (keyShouldBeBlack) {
+ fprintf(stderr, "Weak mapping key %p of map %p should be black\n",
+ aKey.asCell(), aMap);
+ mFailed = true;
+ }
+
+ if (valueShouldBeBlack) {
+ fprintf(stderr, "Weak mapping value %p of map %p should be black\n",
+ aValue.asCell(), aMap);
+ mFailed = true;
+ }
+ }
+
+ bool mFailed;
+};
+#endif // DEBUG
+
+static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing,
+ const char* aName,
+ void* aClosure) {
+ bool* cycleCollectionEnabled = static_cast<bool*>(aClosure);
+
+ if (*cycleCollectionEnabled) {
+ return;
+ }
+
+ if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGrayInCC(aThing)) {
+ *cycleCollectionEnabled = true;
+ }
+}
+
+NS_IMETHODIMP
+JSGCThingParticipant::TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) {
+ auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
+ reinterpret_cast<char*>(this) -
+ offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal));
+
+ JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr));
+ runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr,
+ aCb);
+ return NS_OK;
+}
+
+// NB: This is only used to initialize the participant in
+// CycleCollectedJSRuntime. It should never be used directly.
+static JSGCThingParticipant sGCThingCycleCollectorGlobal;
+
+NS_IMETHODIMP
+JSZoneParticipant::TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) {
+ auto runtime = reinterpret_cast<CycleCollectedJSRuntime*>(
+ reinterpret_cast<char*>(this) -
+ offsetof(CycleCollectedJSRuntime, mJSZoneCycleCollectorGlobal));
+
+ MOZ_ASSERT(!aCb.WantAllTraces());
+ JS::Zone* zone = static_cast<JS::Zone*>(aPtr);
+
+ runtime->TraverseZone(zone, aCb);
+ return NS_OK;
+}
+
+struct TraversalTracer : public JS::CallbackTracer {
+ TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
+ : JS::CallbackTracer(aRt, JS::TracerKind::Callback,
+ JS::TraceOptions(JS::WeakMapTraceAction::Skip,
+ JS::WeakEdgeTraceAction::Trace)),
+ mCb(aCb) {}
+ void onChild(JS::GCCellPtr aThing, const char* name) override;
+ nsCycleCollectionTraversalCallback& mCb;
+};
+
+void TraversalTracer::onChild(JS::GCCellPtr aThing, const char* name) {
+ // Checking strings and symbols for being gray is rather slow, and we don't
+ // need either of them for the cycle collector.
+ if (aThing.is<JSString>() || aThing.is<JS::Symbol>()) {
+ return;
+ }
+
+ // Don't traverse non-gray objects, unless we want all traces.
+ if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) {
+ return;
+ }
+
+ /*
+ * This function needs to be careful to avoid stack overflow. Normally, when
+ * IsCCTraceKind is true, the recursion terminates immediately as we just add
+ * |thing| to the CC graph. So overflow is only possible when there are long
+ * or cyclic chains of non-IsCCTraceKind GC things. Places where this can
+ * occur use special APIs to handle such chains iteratively.
+ */
+ if (JS::IsCCTraceKind(aThing.kind())) {
+ if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
+ char buffer[200];
+ context().getEdgeName(name, buffer, sizeof(buffer));
+ mCb.NoteNextEdgeName(buffer);
+ }
+ mCb.NoteJSChild(aThing);
+ return;
+ }
+
+ // Allow re-use of this tracer inside trace callback.
+ JS::AutoClearTracingContext actc(this);
+
+ if (aThing.is<js::Shape>()) {
+ // The maximum depth of traversal when tracing a Shape is unbounded, due to
+ // the parent pointers on the shape.
+ JS_TraceShapeCycleCollectorChildren(this, aThing);
+ } else {
+ JS::TraceChildren(this, aThing);
+ }
+}
+
+/*
+ * The cycle collection participant for a Zone is intended to produce the same
+ * results as if all of the gray GCthings in a zone were merged into a single
+ * node, except for self-edges. This avoids the overhead of representing all of
+ * the GCthings in the zone in the cycle collector graph, which should be much
+ * faster if many of the GCthings in the zone are gray.
+ *
+ * Zone merging should not always be used, because it is a conservative
+ * approximation of the true cycle collector graph that can incorrectly identify
+ * some garbage objects as being live. For instance, consider two cycles that
+ * pass through a zone, where one is garbage and the other is live. If we merge
+ * the entire zone, the cycle collector will think that both are alive.
+ *
+ * We don't have to worry about losing track of a garbage cycle, because any
+ * such garbage cycle incorrectly identified as live must contain at least one
+ * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph.
+ * (This is in contrast to pure C++ garbage cycles, which must always be
+ * properly identified, because we clear the purple buffer during every CC,
+ * which may contain the last reference to a garbage cycle.)
+ */
+
+// NB: This is only used to initialize the participant in
+// CycleCollectedJSRuntime. It should never be used directly.
+static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
+
+static void JSObjectsTenuredCb(JSContext* aContext, void* aData) {
+ static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured();
+}
+
+static void MozCrashWarningReporter(JSContext*, JSErrorReport*) {
+ MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
+}
+
+JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {}
+
+JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone)
+ : mHolder(aHolder),
+ mTracer(aTracer)
+#ifdef DEBUG
+ ,
+ mZone(aZone)
+#endif
+{
+}
+
+void JSHolderMap::EntryVectorIter::Settle() {
+ if (Done()) {
+ return;
+ }
+
+ Entry* entry = &mIter.Get();
+
+ // If the entry has been cleared, remove it and shrink the vector.
+ if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) {
+ // We removed the last entry, so reset the iterator to an empty one.
+ mIter = EntryVector().Iter();
+ MOZ_ASSERT(Done());
+ }
+}
+
+JSHolderMap::Iter::Iter(JSHolderMap& aMap, WhichHolders aWhich)
+ : mHolderMap(aMap), mIter(aMap, aMap.mAnyZoneJSHolders) {
+ MOZ_RELEASE_ASSERT(!mHolderMap.mHasIterator);
+ mHolderMap.mHasIterator = true;
+
+ // Populate vector of zones to iterate after the any-zone holders.
+ for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) {
+ JS::Zone* zone = i.get().key();
+ if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) {
+ MOZ_ALWAYS_TRUE(mZones.append(zone));
+ }
+ }
+
+ Settle();
+}
+
+void JSHolderMap::Iter::Settle() {
+ while (mIter.Done()) {
+ if (mZone && mIter.Vector().IsEmpty()) {
+ mHolderMap.mPerZoneJSHolders.remove(mZone);
+ }
+
+ mZone = nullptr;
+ if (mZones.empty()) {
+ break;
+ }
+
+ mZone = mZones.popCopy();
+ EntryVector& vector = *mHolderMap.mPerZoneJSHolders.lookup(mZone)->value();
+ new (&mIter) EntryVectorIter(mHolderMap, vector);
+ }
+}
+
+void JSHolderMap::Iter::UpdateForRemovals() {
+ mIter.Settle();
+ Settle();
+}
+
+JSHolderMap::JSHolderMap() : mJSHolderMap(256) {}
+
+bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) {
+ MOZ_ASSERT(aEntry);
+ MOZ_ASSERT(!aEntry->mHolder);
+
+ // Remove all dead entries from the end of the vector.
+ while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) {
+ aJSHolders.PopLast();
+ }
+
+ // Swap the element we want to remove with the last one and update the hash
+ // table.
+ Entry* lastEntry = &aJSHolders.GetLast();
+ if (aEntry != lastEntry) {
+ MOZ_ASSERT(lastEntry->mHolder);
+ *aEntry = *lastEntry;
+ MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder));
+ MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry));
+ }
+
+ aJSHolders.PopLast();
+
+ // Return whether aEntry is still in the vector.
+ return aEntry != lastEntry;
+}
+
+bool JSHolderMap::Has(void* aHolder) const { return mJSHolderMap.has(aHolder); }
+
+nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const {
+ auto ptr = mJSHolderMap.lookup(aHolder);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ Entry* entry = ptr->value();
+ MOZ_ASSERT(entry->mHolder == aHolder);
+ return entry->mTracer;
+}
+
+nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) {
+ MOZ_ASSERT(aHolder);
+
+ auto ptr = mJSHolderMap.lookup(aHolder);
+ if (!ptr) {
+ return nullptr;
+ }
+
+ Entry* entry = ptr->value();
+ MOZ_ASSERT(entry->mHolder == aHolder);
+ nsScriptObjectTracer* tracer = entry->mTracer;
+
+ // Clear the entry's contents. It will be removed the next time iteration
+ // visits this entry.
+ *entry = Entry();
+
+ mJSHolderMap.remove(ptr);
+
+ return tracer;
+}
+
+void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone) {
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(aTracer);
+
+ // Don't associate multi-zone holders with a zone, even if one is supplied.
+ if (!aTracer->IsSingleZoneJSHolder()) {
+ aZone = nullptr;
+ }
+
+ auto ptr = mJSHolderMap.lookupForAdd(aHolder);
+ if (ptr) {
+ Entry* entry = ptr->value();
+#ifdef DEBUG
+ MOZ_ASSERT(entry->mHolder == aHolder);
+ MOZ_ASSERT(entry->mTracer == aTracer,
+ "Don't call HoldJSObjects in superclass ctors");
+ if (aZone) {
+ if (entry->mZone) {
+ MOZ_ASSERT(entry->mZone == aZone);
+ } else {
+ entry->mZone = aZone;
+ }
+ }
+#endif
+ entry->mTracer = aTracer;
+ return;
+ }
+
+ EntryVector* vector = &mAnyZoneJSHolders;
+ if (aZone) {
+ auto ptr = mPerZoneJSHolders.lookupForAdd(aZone);
+ if (!ptr) {
+ MOZ_ALWAYS_TRUE(
+ mPerZoneJSHolders.add(ptr, aZone, MakeUnique<EntryVector>()));
+ }
+ vector = ptr->value().get();
+ }
+
+ vector->InfallibleAppend(Entry{aHolder, aTracer, aZone});
+ MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast()));
+}
+
+size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+
+ // We're deliberately not measuring anything hanging off the entries in
+ // mJSHolders.
+ n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
+ n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) {
+ n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+static bool InitializeShadowRealm(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal) {
+ MOZ_ASSERT(StaticPrefs::javascript_options_experimental_shadow_realms());
+
+ JSAutoRealm ar(aCx, aGlobal);
+ return dom::RegisterShadowRealmBindings(aCx, aGlobal);
+}
+
+CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
+ : mContext(nullptr),
+ mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal),
+ mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal),
+ mJSRuntime(JS_GetRuntime(aCx)),
+ mHasPendingIdleGCTask(false),
+ mPrevGCSliceCallback(nullptr),
+ mOutOfMemoryState(OOMState::OK),
+ mLargeAllocationFailureState(OOMState::OK)
+#ifdef DEBUG
+ ,
+ mShutdownCalled(false)
+#endif
+{
+ MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(mJSRuntime);
+
+#if defined(XP_MACOSX)
+ if (!XRE_IsParentProcess()) {
+ nsMacUtilsImpl::EnableTCSMIfAvailable();
+ }
+#endif
+
+ if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
+ MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
+ }
+ JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
+ JS_SetGCCallback(aCx, GCCallback, this);
+ mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback);
+
+ if (NS_IsMainThread()) {
+ // We would like to support all threads here, but the way timeline consumers
+ // are set up currently, you can either add a marker for one specific
+ // docshell, or for every consumer globally. We would like to add a marker
+ // for every consumer observing anything on this thread, but that is not
+ // currently possible. For now, add global markers only when we are on the
+ // main thread, since the UI for this tracing data only displays data
+ // relevant to the main-thread.
+ JS::AddGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback,
+ nullptr);
+ }
+
+ JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
+ JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
+ JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback,
+ sizeof(dom::AutoYieldJSThreadExecution));
+ JS::SetWarningReporter(aCx, MozCrashWarningReporter);
+ JS::SetShadowRealmInitializeGlobalCallback(aCx, InitializeShadowRealm);
+ JS::SetShadowRealmGlobalCreationCallback(aCx, dom::NewShadowRealmGlobal);
+
+ js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
+ CrashReporter::AnnotateOOMAllocationSize);
+
+ static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
+ SetDOMCallbacks(aCx, &DOMcallbacks);
+ js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer);
+
+ JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of);
+
+#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
+ JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor);
+#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
+
+ JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+class JSLeakTracer : public JS::CallbackTracer {
+ public:
+ explicit JSLeakTracer(JSRuntime* aRuntime)
+ : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback,
+ JS::WeakMapTraceAction::TraceKeysAndValues) {}
+
+ private:
+ void onChild(JS::GCCellPtr thing, const char* name) override {
+ const char* kindName = JS::GCTraceKindToAscii(thing.kind());
+ size_t size = JS::GCTraceKindSize(thing.kind());
+ MOZ_LOG_CTOR(thing.asCell(), kindName, size);
+ }
+};
+#endif
+
+void CycleCollectedJSRuntime::Shutdown(JSContext* aCx) {
+#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
+ mErrorInterceptor.Shutdown(mJSRuntime);
+#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
+
+ // There should not be any roots left to trace at this point. Ensure any that
+ // remain are flagged as leaks.
+#ifdef NS_BUILD_REFCNT_LOGGING
+ JSLeakTracer tracer(Runtime());
+ TraceNativeBlackRoots(&tracer);
+ TraceAllNativeGrayRoots(&tracer);
+#endif
+
+#ifdef DEBUG
+ mShutdownCalled = true;
+#endif
+
+ JS_SetDestroyZoneCallback(aCx, nullptr);
+
+ if (NS_IsMainThread()) {
+ JS::RemoveGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback,
+ nullptr);
+ }
+}
+
+CycleCollectedJSRuntime::~CycleCollectedJSRuntime() {
+ MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
+ MOZ_ASSERT(!mDeferredFinalizerTable.Count());
+ MOZ_ASSERT(!mFinalizeRunnable);
+ MOZ_ASSERT(mShutdownCalled);
+}
+
+void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) {
+ MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!");
+ mContext = aContext;
+}
+
+size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
+ for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
+ entry->mTracer->CanSkip(entry->mHolder, true);
+ }
+}
+
+void CycleCollectedJSRuntime::DescribeGCThing(
+ bool aIsMarked, JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) const {
+ if (!aCb.WantDebugInfo()) {
+ aCb.DescribeGCedNode(aIsMarked, "JS Object");
+ return;
+ }
+
+ char name[72];
+ uint64_t compartmentAddress = 0;
+ if (aThing.is<JSObject>()) {
+ JSObject* obj = &aThing.as<JSObject>();
+ compartmentAddress = (uint64_t)JS::GetCompartment(obj);
+ const JSClass* clasp = JS::GetClass(obj);
+
+ // Give the subclass a chance to do something
+ if (DescribeCustomObjects(obj, clasp, name)) {
+ // Nothing else to do!
+ } else if (js::IsFunctionObject(obj)) {
+ JSFunction* fun = JS_GetObjectFunction(obj);
+ JSString* str = JS_GetMaybePartialFunctionDisplayId(fun);
+ if (str) {
+ JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str);
+ nsAutoString chars;
+ AssignJSLinearString(chars, linear);
+ NS_ConvertUTF16toUTF8 fname(chars);
+ SprintfLiteral(name, "JS Object (Function - %s)", fname.get());
+ } else {
+ SprintfLiteral(name, "JS Object (Function)");
+ }
+ } else {
+ SprintfLiteral(name, "JS Object (%s)", clasp->name);
+ }
+ } else {
+ SprintfLiteral(name, "%s", JS::GCTraceKindToAscii(aThing.kind()));
+ }
+
+ // Disable printing global for objects while we figure out ObjShrink fallout.
+ aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress);
+}
+
+void CycleCollectedJSRuntime::NoteGCThingJSChildren(
+ JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const {
+ TraversalTracer trc(mJSRuntime, aCb);
+ JS::TraceChildren(&trc, aThing);
+}
+
+void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren(
+ const JSClass* aClasp, JSObject* aObj,
+ nsCycleCollectionTraversalCallback& aCb) const {
+ MOZ_ASSERT(aClasp);
+ MOZ_ASSERT(aClasp == JS::GetClass(aObj));
+
+ JS::Rooted<JSObject*> obj(RootingCx(), aObj);
+
+ if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) {
+ // Nothing else to do!
+ return;
+ }
+
+ // XXX This test does seem fragile, we should probably allowlist classes
+ // that do hold a strong reference, but that might not be possible.
+ if (aClasp->slot0IsISupports()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)");
+ aCb.NoteXPCOMChild(JS::GetObjectISupports<nsISupports>(obj));
+ return;
+ }
+
+ const DOMJSClass* domClass = GetDOMClass(aClasp);
+ if (domClass) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)");
+ // It's possible that our object is an unforgeable holder object, in
+ // which case it doesn't actually have a C++ DOM object associated with
+ // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in
+ // that case, since NoteXPCOMChild/NoteNativeChild are null-safe.
+ if (domClass->mDOMObjectIsISupports) {
+ aCb.NoteXPCOMChild(
+ UnwrapPossiblyNotInitializedDOMObject<nsISupports>(obj));
+ } else if (domClass->mParticipant) {
+ aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject<void>(obj),
+ domClass->mParticipant);
+ }
+ return;
+ }
+
+ if (IsRemoteObjectProxy(obj)) {
+ auto handler =
+ static_cast<const RemoteObjectProxyBase*>(js::GetProxyHandler(obj));
+ return handler->NoteChildren(obj, aCb);
+ }
+
+ JS::Value value = js::MaybeGetScriptPrivate(obj);
+ if (!value.isUndefined()) {
+ aCb.NoteXPCOMChild(static_cast<nsISupports*>(value.toPrivate()));
+ }
+}
+
+void CycleCollectedJSRuntime::TraverseGCThing(
+ TraverseSelect aTs, JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) {
+ bool isMarkedGray = JS::GCThingIsMarkedGrayInCC(aThing);
+
+ if (aTs == TRAVERSE_FULL) {
+ DescribeGCThing(!isMarkedGray, aThing, aCb);
+ }
+
+ // If this object is alive, then all of its children are alive. For JS
+ // objects, the black-gray invariant ensures the children are also marked
+ // black. For C++ objects, the ref count from this object will keep them
+ // alive. Thus we don't need to trace our children, unless we are debugging
+ // using WantAllTraces.
+ if (!isMarkedGray && !aCb.WantAllTraces()) {
+ return;
+ }
+
+ if (aTs == TRAVERSE_FULL) {
+ NoteGCThingJSChildren(aThing, aCb);
+ }
+
+ if (aThing.is<JSObject>()) {
+ JSObject* obj = &aThing.as<JSObject>();
+ NoteGCThingXPCOMChildren(JS::GetClass(obj), obj, aCb);
+ }
+}
+
+struct TraverseObjectShimClosure {
+ nsCycleCollectionTraversalCallback& cb;
+ CycleCollectedJSRuntime* self;
+};
+
+void CycleCollectedJSRuntime::TraverseZone(
+ JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) {
+ /*
+ * We treat the zone as being gray. We handle non-gray GCthings in the
+ * zone by not reporting their children to the CC. The black-gray invariant
+ * ensures that any JS children will also be non-gray, and thus don't need to
+ * be added to the graph. For C++ children, not representing the edge from the
+ * non-gray JS GCthings to the C++ object will keep the child alive.
+ *
+ * We don't allow zone merging in a WantAllTraces CC, because then these
+ * assumptions don't hold.
+ */
+ aCb.DescribeGCedNode(false, "JS Zone");
+
+ /*
+ * Every JS child of everything in the zone is either in the zone
+ * or is a cross-compartment wrapper. In the former case, we don't need to
+ * represent these edges in the CC graph because JS objects are not ref
+ * counted. In the latter case, the JS engine keeps a map of these wrappers,
+ * which we iterate over. Edges between compartments in the same zone will add
+ * unnecessary loop edges to the graph (bug 842137).
+ */
+ TraversalTracer trc(mJSRuntime, aCb);
+ js::TraceGrayWrapperTargets(&trc, aZone);
+
+ /*
+ * To find C++ children of things in the zone, we scan every JS Object in
+ * the zone. Only JS Objects can have C++ children.
+ */
+ TraverseObjectShimClosure closure = {aCb, this};
+ js::IterateGrayObjects(aZone, TraverseObjectShim, &closure);
+}
+
+/* static */
+void CycleCollectedJSRuntime::TraverseObjectShim(
+ void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) {
+ TraverseObjectShimClosure* closure =
+ static_cast<TraverseObjectShimClosure*>(aData);
+
+ MOZ_ASSERT(aThing.is<JSObject>());
+ closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing,
+ closure->cb);
+}
+
+void CycleCollectedJSRuntime::TraverseNativeRoots(
+ nsCycleCollectionNoteRootCallback& aCb) {
+ // NB: This is here just to preserve the existing XPConnect order. I doubt it
+ // would hurt to do this after the JS holders.
+ TraverseAdditionalNativeRoots(aCb);
+
+ for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) {
+ void* holder = entry->mHolder;
+ nsScriptObjectTracer* tracer = entry->mTracer;
+
+ bool noteRoot = false;
+ if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
+ noteRoot = true;
+ } else {
+ tracer->Trace(holder,
+ TraceCallbackFunc(CheckParticipatesInCycleCollection),
+ &noteRoot);
+ }
+
+ if (noteRoot) {
+ aCb.NoteNativeRoot(holder, tracer);
+ }
+ }
+}
+
+/* static */
+void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) {
+ CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
+
+ self->TraceNativeBlackRoots(aTracer);
+}
+
+/* static */
+bool CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer,
+ js::SliceBudget& budget,
+ void* aData) {
+ CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
+
+ // Mark these roots as gray so the CC can walk them later.
+
+ JSHolderMap::WhichHolders which = JSHolderMap::AllHolders;
+
+ // Only trace holders in collecting zones when marking, except if we are
+ // collecting the atoms zone since any holder may point into that zone.
+ if (aTracer->isMarkingTracer() &&
+ !JS::AtomsZoneIsCollecting(self->Runtime())) {
+ which = JSHolderMap::HoldersRequiredForGrayMarking;
+ }
+
+ return self->TraceNativeGrayRoots(aTracer, which, budget);
+}
+
+/* static */
+void CycleCollectedJSRuntime::GCCallback(JSContext* aContext,
+ JSGCStatus aStatus,
+ JS::GCReason aReason, void* aData) {
+ CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
+
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
+
+ self->OnGC(aContext, aStatus, aReason);
+}
+
+struct GCMajorMarker : public BaseMarkerType<GCMajorMarker> {
+ static constexpr const char* Name = "GCMajor";
+ static constexpr const char* Description =
+ "Summary data for an entire major GC, encompassing a set of "
+ "incremental slices. The main thread is not blocked for the "
+ "entire major GC interval, only for the individual slices.";
+
+ using MS = MarkerSchema;
+ static constexpr MS::PayloadField PayloadFields[] = {
+ {"timings", MS::InputType::String, "GC timings"}};
+
+ static constexpr MS::Location Locations[] = {MS::Location::MarkerChart,
+ MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ static constexpr MS::ETWMarkerGroup Group = MS::ETWMarkerGroup::Memory;
+
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aTimingJSON) {
+ if (aTimingJSON.Length() != 0) {
+ aWriter.SplicedJSONProperty("timings", aTimingJSON);
+ } else {
+ aWriter.NullProperty("timings");
+ }
+ }
+};
+
+/* static */
+void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext,
+ JS::GCProgress aProgress,
+ const JS::GCDescription& aDesc) {
+ CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ if (aProgress == JS::GC_CYCLE_END) {
+ profiler_add_marker("GCMajor", baseprofiler::category::GCCC,
+ MarkerTiming::Interval(aDesc.startTime(aContext),
+ aDesc.endTime(aContext)),
+ GCMajorMarker{},
+ ProfilerString8View::WrapNullTerminatedString(
+ aDesc.formatJSONProfiler(aContext).get()));
+ } else if (aProgress == JS::GC_SLICE_END) {
+ struct GCSliceMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("GCSlice");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aTimingJSON) {
+ if (aTimingJSON.Length() != 0) {
+ aWriter.SplicedJSONProperty("timings", aTimingJSON);
+ } else {
+ aWriter.NullProperty("timings");
+ }
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ using MS = mozilla::MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ schema.AddStaticLabelValue(
+ "Description",
+ "One slice of an incremental garbage collection (GC). The main "
+ "thread is blocked during this time.");
+ // No display instructions here, there is special handling in the
+ // front-end.
+ return schema;
+ }
+ };
+
+ profiler_add_marker("GCSlice", baseprofiler::category::GCCC,
+ MarkerTiming::Interval(aDesc.lastSliceStart(aContext),
+ aDesc.lastSliceEnd(aContext)),
+ GCSliceMarker{},
+ ProfilerString8View::WrapNullTerminatedString(
+ aDesc.sliceToJSONProfiler(aContext).get()));
+ }
+ }
+
+ if (aProgress == JS::GC_CYCLE_END &&
+ JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) {
+ JS::GCReason reason = aDesc.reason_;
+ Unused << NS_WARN_IF(
+ NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) &&
+ reason != JS::GCReason::SHUTDOWN_CC &&
+ reason != JS::GCReason::DESTROY_RUNTIME &&
+ reason != JS::GCReason::XPCONNECT_SHUTDOWN);
+ }
+
+ if (self->mPrevGCSliceCallback) {
+ self->mPrevGCSliceCallback(aContext, aProgress, aDesc);
+ }
+}
+
+/* static */
+void CycleCollectedJSRuntime::GCNurseryCollectionCallback(
+ JSContext* aContext, JS::GCNurseryProgress aProgress, JS::GCReason aReason,
+ void* data) {
+ CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TimeStamp now = TimeStamp::Now();
+ if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) {
+ self->mLatestNurseryCollectionStart = now;
+ } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) {
+ PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC,
+ now - self->mLatestNurseryCollectionStart);
+ }
+
+ if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END &&
+ profiler_thread_is_being_profiled_for_markers()) {
+ struct GCMinorMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("GCMinor");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString8View& aTimingJSON) {
+ if (aTimingJSON.Length() != 0) {
+ aWriter.SplicedJSONProperty("nursery", aTimingJSON);
+ } else {
+ aWriter.NullProperty("nursery");
+ }
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ using MS = mozilla::MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineMemory};
+ schema.AddStaticLabelValue(
+ "Description",
+ "A minor GC (aka nursery collection) to clear out the buffer used "
+ "for recent allocations and move surviving data to the tenured "
+ "(long-lived) heap.");
+ // No display instructions here, there is special handling in the
+ // front-end.
+ return schema;
+ }
+ };
+
+ profiler_add_marker(
+ "GCMinor", baseprofiler::category::GCCC,
+ MarkerTiming::Interval(self->mLatestNurseryCollectionStart, now),
+ GCMinorMarker{},
+ ProfilerString8View::WrapNullTerminatedString(
+ JS::MinorGcToJSON(aContext).get()));
+ }
+}
+
+/* static */
+void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
+ void* aData) {
+ CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
+
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext);
+ MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self);
+
+ self->OnOutOfMemory();
+}
+
+/* static */
+void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) {
+ MOZ_ASSERT(aMemory);
+
+ // aMemory is stack allocated memory to contain our RAII object. This allows
+ // for us to avoid allocations on the heap during this callback.
+ return new (aMemory) dom::AutoYieldJSThreadExecution;
+}
+
+/* static */
+void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) {
+ MOZ_ASSERT(aCookie);
+ static_cast<dom::AutoYieldJSThreadExecution*>(aCookie)
+ ->~AutoYieldJSThreadExecution();
+}
+
+struct JsGcTracer : public TraceCallbacks {
+ virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const override {
+ aPtr->TraceWrapper(static_cast<JSTracer*>(aClosure), aName);
+ }
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+ virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::TraceEdge(static_cast<JSTracer*>(aClosure), aPtr, aName);
+ }
+};
+
+void mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) {
+ nsXPCOMCycleCollectionParticipant* participant = nullptr;
+ CallQueryInterface(aHolder, &participant);
+ participant->Trace(aHolder, JsGcTracer(), aTracer);
+}
+
+#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
+# define CHECK_SINGLE_ZONE_JS_HOLDERS
+#endif
+
+#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
+
+// A tracer that checks that a JS holder only holds JS GC things in a single
+// JS::Zone.
+struct CheckZoneTracer : public TraceCallbacks {
+ const char* mClassName;
+ mutable JS::Zone* mZone;
+
+ explicit CheckZoneTracer(const char* aClassName, JS::Zone* aZone = nullptr)
+ : mClassName(aClassName), mZone(aZone) {}
+
+ void checkZone(JS::Zone* aZone, const char* aName) const {
+ if (JS::IsAtomsZone(aZone)) {
+ // Any holder may contain pointers into the atoms zone.
+ return;
+ }
+
+ if (!mZone) {
+ mZone = aZone;
+ return;
+ }
+
+ if (aZone == mZone) {
+ return;
+ }
+
+ // Most JS holders only contain pointers to GC things in a single zone. We
+ // group holders by referent zone where possible, allowing us to improve GC
+ // performance by only tracing holders for zones that are being collected.
+ //
+ // Additionally, pointers from any holder into the atoms zone are allowed
+ // since all holders are traced when we collect the atoms zone.
+ //
+ // If you added a holder that has pointers into multiple zones do not
+ // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS.
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "JS holder %s contains pointers to GC things in more than one zone ("
+ "found in %s)\n",
+ mClassName, aName);
+ }
+
+ virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JS::Value value = aPtr->unbarrieredGet();
+ if (value.isGCThing()) {
+ checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName);
+ }
+ }
+ virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
+ void* aClosure) const override {
+ jsid id = aPtr->unbarrieredGet();
+ if (id.isGCThing()) {
+ MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr())));
+ }
+ }
+ virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSObject* obj = aPtr->unbarrieredGet();
+ if (obj) {
+ checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
+ }
+ }
+ virtual void Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSObject* obj = aPtr->GetWrapperPreserveColor();
+ if (obj) {
+ checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
+ }
+ }
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSObject* obj = aPtr->unbarrieredGetPtr();
+ if (obj) {
+ checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
+ }
+ }
+ virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSString* str = aPtr->unbarrieredGet();
+ if (str) {
+ checkZone(JS::GetStringZone(str), aName);
+ }
+ }
+ virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSScript* script = aPtr->unbarrieredGet();
+ if (script) {
+ checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName);
+ }
+ }
+ virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
+ void* aClosure) const override {
+ JSFunction* fun = aPtr->unbarrieredGet();
+ if (fun) {
+ checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)),
+ aName);
+ }
+ }
+};
+
+static inline void CheckHolderIsSingleZone(
+ void* aHolder, nsCycleCollectionParticipant* aParticipant,
+ JS::Zone* aZone) {
+ CheckZoneTracer tracer(aParticipant->ClassName(), aZone);
+ aParticipant->Trace(aHolder, tracer, nullptr);
+}
+
+#endif
+
+static inline bool ShouldCheckSingleZoneHolders() {
+#if defined(DEBUG)
+ return true;
+#elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
+ // Don't check every time to avoid performance impact.
+ return rand() % 256 == 0;
+#else
+ return false;
+#endif
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+void CycleCollectedJSRuntime::TraceAllNativeGrayRoots(JSTracer* aTracer) {
+ MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
+ js::SliceBudget budget = js::SliceBudget::unlimited();
+ MOZ_ALWAYS_TRUE(
+ TraceNativeGrayRoots(aTracer, JSHolderMap::AllHolders, budget));
+}
+#endif
+
+bool CycleCollectedJSRuntime::TraceNativeGrayRoots(
+ JSTracer* aTracer, JSHolderMap::WhichHolders aWhich,
+ js::SliceBudget& aBudget) {
+ if (!mHolderIter) {
+ // NB: This is here just to preserve the existing XPConnect order. I doubt
+ // it would hurt to do this after the JS holders.
+ TraceAdditionalNativeGrayRoots(aTracer);
+
+ mHolderIter.emplace(mJSHolders, aWhich);
+ aBudget.forceCheck();
+ } else {
+ // Holders may have been removed between slices, so we may need to update
+ // the iterator.
+ mHolderIter->UpdateForRemovals();
+ }
+
+ bool finished = TraceJSHolders(aTracer, *mHolderIter, aBudget);
+ if (finished) {
+ mHolderIter.reset();
+ }
+
+ return finished;
+}
+
+bool CycleCollectedJSRuntime::TraceJSHolders(JSTracer* aTracer,
+ JSHolderMap::Iter& aIter,
+ js::SliceBudget& aBudget) {
+ bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders();
+
+ while (!aIter.Done() && !aBudget.isOverBudget()) {
+ void* holder = aIter->mHolder;
+ nsScriptObjectTracer* tracer = aIter->mTracer;
+
+#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS
+ if (checkSingleZoneHolders && tracer->IsSingleZoneJSHolder()) {
+ CheckHolderIsSingleZone(holder, tracer, aIter.Zone());
+ }
+#else
+ Unused << checkSingleZoneHolders;
+#endif
+
+ tracer->Trace(holder, JsGcTracer(), aTracer);
+
+ aIter.Next();
+ aBudget.step();
+ }
+
+ return aIter.Done();
+}
+
+void CycleCollectedJSRuntime::AddJSHolder(void* aHolder,
+ nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone) {
+ mJSHolders.Put(aHolder, aTracer, aZone);
+}
+
+struct ClearJSHolder : public TraceCallbacks {
+ virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*,
+ void*) const override {
+ aPtr->setUndefined();
+ }
+
+ virtual void Trace(JS::Heap<jsid>* aPtr, const char*, void*) const override {
+ *aPtr = JS::PropertyKey::Void();
+ }
+
+ virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*,
+ void*) const override {
+ *aPtr = nullptr;
+ }
+
+ virtual void Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const override {
+ aPtr->ClearWrapper();
+ }
+
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*,
+ void*) const override {
+ *aPtr = nullptr;
+ }
+
+ virtual void Trace(JS::Heap<JSString*>* aPtr, const char*,
+ void*) const override {
+ *aPtr = nullptr;
+ }
+
+ virtual void Trace(JS::Heap<JSScript*>* aPtr, const char*,
+ void*) const override {
+ *aPtr = nullptr;
+ }
+
+ virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*,
+ void*) const override {
+ *aPtr = nullptr;
+ }
+};
+
+void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
+ nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder);
+ if (tracer) {
+ // Bug 1531951: The analysis can't see through the virtual call but we know
+ // that the ClearJSHolder tracer will never GC.
+ JS::AutoSuppressGCAnalysis nogc;
+ tracer->Trace(aHolder, ClearJSHolder(), nullptr);
+ }
+}
+
+#ifdef DEBUG
+static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName,
+ void* aClosure) {
+ MOZ_ASSERT(!aGCThing);
+}
+
+void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) {
+ nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder);
+ if (tracer) {
+ tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing),
+ nullptr);
+ }
+}
+#endif
+
+nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() {
+ return &mGCThingCycleCollectorGlobal;
+}
+
+nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() {
+ return &mJSZoneCycleCollectorGlobal;
+}
+
+nsresult CycleCollectedJSRuntime::TraverseRoots(
+ nsCycleCollectionNoteRootCallback& aCb) {
+ TraverseNativeRoots(aCb);
+
+ NoteWeakMapsTracer trc(mJSRuntime, aCb);
+ js::TraceWeakMaps(&trc);
+
+ return NS_OK;
+}
+
+bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; }
+
+void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const {
+ MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
+ "Don't call FixWeakMappingGrayBits during a GC.");
+ FixWeakMappingGrayBitsTracer fixer(mJSRuntime);
+ fixer.FixAll();
+}
+
+void CycleCollectedJSRuntime::CheckGrayBits() const {
+ MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime),
+ "Don't call CheckGrayBits during a GC.");
+
+#ifndef ANDROID
+ // Bug 1346874 - The gray state check is expensive. Android tests are already
+ // slow enough that this check can easily push them over the threshold to a
+ // timeout.
+
+ MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime));
+ MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime));
+#endif
+}
+
+bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const {
+ return js::AreGCGrayBitsValid(mJSRuntime);
+}
+
+void CycleCollectedJSRuntime::GarbageCollect(JS::GCOptions aOptions,
+ JS::GCReason aReason) const {
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+ JS::PrepareForFullGC(cx);
+ JS::NonIncrementalGC(cx, aOptions, aReason);
+}
+
+void CycleCollectedJSRuntime::JSObjectsTenured() {
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+ for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
+ nsWrapperCache* cache = iter.Get();
+ JSObject* wrapper = cache->GetWrapperMaybeDead();
+ MOZ_DIAGNOSTIC_ASSERT(wrapper);
+ if (!JS::ObjectIsTenured(wrapper)) {
+ MOZ_ASSERT(!cache->PreservingWrapper());
+ js::gc::FinalizeDeadNurseryObject(cx, wrapper);
+ }
+ }
+
+ mNurseryObjects.Clear();
+}
+
+void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) {
+ MOZ_ASSERT(aCache);
+ MOZ_ASSERT(aCache->GetWrapperMaybeDead());
+ MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead()));
+ mNurseryObjects.InfallibleAppend(aCache);
+}
+
+void CycleCollectedJSRuntime::DeferredFinalize(
+ DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
+ void* aThing) {
+ // Tell the analysis that the function pointers will not GC.
+ JS::AutoSuppressGCAnalysis suppress;
+ mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) {
+ if (entry) {
+ aAppendFunc(entry.Data(), aThing);
+ } else {
+ entry.Insert(aAppendFunc(nullptr, aThing));
+ }
+ });
+}
+
+void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) {
+ typedef DeferredFinalizerImpl<nsISupports> Impl;
+ DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize,
+ aSupports);
+}
+
+void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) {
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+
+ mozilla::MallocSizeOf mallocSizeOf =
+ PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr;
+ js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf);
+}
+
+IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(
+ CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers)
+ : DiscardableRunnable("IncrementalFinalizeRunnable"),
+ mRuntime(aRt),
+ mFinalizeFunctionToRun(0),
+ mReleasing(false) {
+ for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) {
+ DeferredFinalizeFunction& function = iter.Key();
+ void*& data = iter.Data();
+
+ DeferredFinalizeFunctionHolder* holder =
+ mDeferredFinalizeFunctions.AppendElement();
+ holder->run = function;
+ holder->data = data;
+
+ iter.Remove();
+ }
+ MOZ_ASSERT(mDeferredFinalizeFunctions.Length());
+}
+
+IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() {
+ MOZ_ASSERT(!mDeferredFinalizeFunctions.Length());
+ MOZ_ASSERT(!mRuntime);
+}
+
+void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) {
+ if (mReleasing) {
+ NS_WARNING("Re-entering ReleaseNow");
+ return;
+ }
+ {
+ AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow",
+ GCCC_Finalize);
+
+ mozilla::AutoRestore<bool> ar(mReleasing);
+ mReleasing = true;
+ MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0,
+ "We should have at least ReleaseSliceNow to run");
+ MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(),
+ "No more finalizers to run?");
+
+ TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
+ TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp();
+ bool timeout = false;
+ do {
+ const DeferredFinalizeFunctionHolder& function =
+ mDeferredFinalizeFunctions[mFinalizeFunctionToRun];
+ if (aLimited) {
+ bool done = false;
+ while (!timeout && !done) {
+ /*
+ * We don't want to read the clock too often, so we try to
+ * release slices of 100 items.
+ */
+ done = function.run(100, function.data);
+ timeout = TimeStamp::Now() - started >= sliceTime;
+ }
+ if (done) {
+ ++mFinalizeFunctionToRun;
+ }
+ if (timeout) {
+ break;
+ }
+ } else {
+ while (!function.run(UINT32_MAX, function.data))
+ ;
+ ++mFinalizeFunctionToRun;
+ }
+ } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length());
+ }
+
+ if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) {
+ MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
+ mDeferredFinalizeFunctions.Clear();
+ CycleCollectedJSRuntime* runtime = mRuntime;
+ mRuntime = nullptr;
+ // NB: This may delete this!
+ runtime->mFinalizeRunnable = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+IncrementalFinalizeRunnable::Run() {
+ if (!mDeferredFinalizeFunctions.Length()) {
+ /* These items were already processed synchronously in JSGC_END. */
+ MOZ_ASSERT(!mRuntime);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mRuntime->mFinalizeRunnable == this);
+ TimeStamp start = TimeStamp::Now();
+ ReleaseNow(true);
+
+ if (mDeferredFinalizeFunctions.Length()) {
+ nsresult rv = NS_DispatchToCurrentThread(this);
+ if (NS_FAILED(rv)) {
+ ReleaseNow(false);
+ }
+ } else {
+ MOZ_ASSERT(!mRuntime);
+ }
+
+ uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration);
+
+ return NS_OK;
+}
+
+void CycleCollectedJSRuntime::FinalizeDeferredThings(
+ DeferredFinalizeType aType) {
+ // If mFinalizeRunnable isn't null, we didn't finalize everything from the
+ // previous GC.
+ if (mFinalizeRunnable) {
+ if (aType == FinalizeLater) {
+ // We need to defer all finalization until we return to the event loop,
+ // so leave things alone. Any new objects to be finalized from the current
+ // GC will be handled by the existing mFinalizeRunnable.
+ return;
+ }
+ MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow);
+ // If we're finalizing incrementally, we don't want finalizers to build up,
+ // so try to finish them off now.
+ // If we're finalizing synchronously, also go ahead and clear them out,
+ // so we make sure as much as possible is freed.
+ mFinalizeRunnable->ReleaseNow(false);
+ if (mFinalizeRunnable) {
+ // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and
+ // we need to just continue processing it.
+ return;
+ }
+ }
+
+ // If there's nothing to finalize, don't create a new runnable.
+ if (mDeferredFinalizerTable.Count() == 0) {
+ return;
+ }
+
+ mFinalizeRunnable =
+ new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable);
+
+ // Everything should be gone now.
+ MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0);
+
+ if (aType == FinalizeNow) {
+ mFinalizeRunnable->ReleaseNow(false);
+ MOZ_ASSERT(!mFinalizeRunnable);
+ } else {
+ MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeLater);
+ NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500,
+ EventQueuePriority::Idle);
+ }
+}
+
+const char* CycleCollectedJSRuntime::OOMStateToString(
+ const OOMState aOomState) const {
+ switch (aOomState) {
+ case OOMState::OK:
+ return "OK";
+ case OOMState::Reporting:
+ return "Reporting";
+ case OOMState::Reported:
+ return "Reported";
+ case OOMState::Recovered:
+ return "Recovered";
+ default:
+ MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value");
+ return "Unknown";
+ }
+}
+
+bool CycleCollectedJSRuntime::OOMReported() {
+ return mOutOfMemoryState == OOMState::Reported;
+}
+
+void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr,
+ OOMState aNewState) {
+ *aStatePtr = aNewState;
+ CrashReporter::Annotation annotation =
+ (aStatePtr == &mOutOfMemoryState)
+ ? CrashReporter::Annotation::JSOutOfMemory
+ : CrashReporter::Annotation::JSLargeAllocationFailure;
+
+ CrashReporter::AnnotateCrashReport(
+ annotation, nsDependentCString(OOMStateToString(aNewState)));
+}
+
+void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus,
+ JS::GCReason aReason) {
+ switch (aStatus) {
+ case JSGC_BEGIN:
+ MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
+ nsCycleCollector_prepareForGarbageCollection();
+ PrepareWaitingZonesForGC();
+ break;
+ case JSGC_END: {
+ MOZ_RELEASE_ASSERT(mHolderIter.isNothing());
+ if (mOutOfMemoryState == OOMState::Reported) {
+ AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered);
+ }
+ if (mLargeAllocationFailureState == OOMState::Reported) {
+ AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState,
+ OOMState::Recovered);
+ }
+
+ DeferredFinalizeType finalizeType;
+ if (JS_IsExceptionPending(aContext)) {
+ // There is a pending exception. The finalizers are not set up to run
+ // in that state, so don't run the finalizer until we've returned to the
+ // event loop.
+ finalizeType = FinalizeLater;
+ } else if (JS::InternalGCReason(aReason)) {
+ if (aReason == JS::GCReason::DESTROY_RUNTIME) {
+ // We're shutting down, so we need to destroy things immediately.
+ finalizeType = FinalizeNow;
+ } else {
+ // We may be in the middle of running some code that the JIT has
+ // assumed can't have certain kinds of side effects. Finalizers can do
+ // all sorts of things, such as run JS, so we want to run them later,
+ // after we've returned to the event loop.
+ finalizeType = FinalizeLater;
+ }
+ } else if (JS::WasIncrementalGC(mJSRuntime)) {
+ // The GC was incremental, so we probably care about pauses. Try to
+ // break up finalization, but it is okay if we do some now.
+ finalizeType = FinalizeIncrementally;
+ } else {
+ // If we're running a synchronous GC, we probably want to free things as
+ // quickly as possible. This can happen during testing or if memory is
+ // low.
+ finalizeType = FinalizeNow;
+ }
+ FinalizeDeferredThings(finalizeType);
+
+ break;
+ }
+ default:
+ MOZ_CRASH();
+ }
+
+ CustomGCCallback(aStatus);
+}
+
+void CycleCollectedJSRuntime::OnOutOfMemory() {
+ AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting);
+ CustomOutOfMemoryCallback();
+ AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported);
+}
+
+void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) {
+ AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState);
+}
+
+void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() {
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+ if (mZonesWaitingForGC.Count() == 0) {
+ JS::PrepareForFullGC(cx);
+ } else {
+ for (const auto& key : mZonesWaitingForGC) {
+ JS::PrepareZoneForGC(cx, key);
+ }
+ mZonesWaitingForGC.Clear();
+ }
+}
+
+/* static */
+void CycleCollectedJSRuntime::OnZoneDestroyed(JS::GCContext* aGcx,
+ JS::Zone* aZone) {
+ // Remove the zone from the set of zones waiting for GC, if present. This can
+ // happen if a zone is added to the set during an incremental GC in which it
+ // is later destroyed.
+ CycleCollectedJSRuntime* runtime = Get();
+ runtime->mZonesWaitingForGC.Remove(aZone);
+}
+
+void CycleCollectedJSRuntime::EnvironmentPreparer::invoke(
+ JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) {
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+ nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global);
+
+ // Not much we can do if we simply don't have a usable global here...
+ NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal());
+
+ AutoEntryScript aes(nativeGlobal, "JS-engine-initiated execution");
+
+ MOZ_ASSERT(!JS_IsExceptionPending(aes.cx()));
+
+ DebugOnly<bool> ok = closure(aes.cx());
+
+ MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx()));
+
+ // The AutoEntryScript will check for JS_IsExceptionPending on the
+ // JSContext and report it as needed as it comes off the stack.
+}
+
+/* static */
+CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() {
+ auto context = CycleCollectedJSContext::Get();
+ if (context) {
+ return context->Runtime();
+ }
+ return nullptr;
+}
+
+#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR
+
+namespace js {
+extern void DumpValue(const JS::Value& val);
+}
+
+void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) {
+ JS_SetErrorInterceptorCallback(rt, nullptr);
+ mThrownError.reset();
+}
+
+/* virtual */
+void CycleCollectedJSRuntime::ErrorInterceptor::interceptError(
+ JSContext* cx, JS::HandleValue exn) {
+ if (mThrownError) {
+ // We already have an error, we don't need anything more.
+ return;
+ }
+
+ if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
+ // We are only interested in chrome code.
+ return;
+ }
+
+ const auto type = JS_GetErrorType(exn);
+ if (!type) {
+ // This is not one of the primitive error types.
+ return;
+ }
+
+ switch (*type) {
+ case JSExnType::JSEXN_REFERENCEERR:
+ case JSExnType::JSEXN_SYNTAXERR:
+ break;
+ default:
+ // Not one of the errors we are interested in.
+ // Note that we are not interested in instances of `TypeError`
+ // for the time being, as DOM (ab)uses this constructor to represent
+ // all sorts of errors that are not even remotely related to type
+ // errors (e.g. some network errors).
+ // If we ever have a mechanism to differentiate between DOM-thrown
+ // and SpiderMonkey-thrown instances of `TypeError`, we should
+ // consider watching for `TypeError` here.
+ return;
+ }
+
+ // Now copy the details of the exception locally.
+ // While copying the details of an exception could be expensive, in most runs,
+ // this will be done at most once during the execution of the process, so the
+ // total cost should be reasonable.
+
+ ErrorDetails details;
+ details.mType = *type;
+ // If `exn` isn't an exception object, `ExtractErrorValues` could end up
+ // calling `toString()`, which could in turn end up throwing an error. While
+ // this should work, we want to avoid that complex use case. Fortunately, we
+ // have already checked above that `exn` is an exception object, so nothing
+ // such should happen.
+ nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine,
+ &details.mColumn, details.mMessage);
+
+ JS::UniqueChars buf =
+ JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false,
+ /* showThisProps = */ false);
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack);
+
+ mThrownError.emplace(std::move(details));
+}
+
+void CycleCollectedJSRuntime::ClearRecentDevError() {
+ mErrorInterceptor.mThrownError.reset();
+}
+
+bool CycleCollectedJSRuntime::GetRecentDevError(
+ JSContext* cx, JS::MutableHandle<JS::Value> error) {
+ if (!mErrorInterceptor.mThrownError) {
+ return true;
+ }
+
+ // Create a copy of the exception.
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+
+ JS::RootedValue message(cx);
+ JS::RootedValue filename(cx);
+ JS::RootedValue stack(cx);
+ if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) ||
+ !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) ||
+ !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) {
+ return false;
+ }
+
+ // Build the object.
+ const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
+ if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) ||
+ !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) ||
+ !JS_DefineProperty(cx, obj, "lineNumber",
+ mErrorInterceptor.mThrownError->mLine, FLAGS) ||
+ !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) {
+ return false;
+ }
+
+ // Pass the result.
+ error.setObject(*obj);
+ return true;
+}
+#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR
+
+#undef MOZ_JS_DEV_ERROR_INTERCEPTOR
diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h
new file mode 100644
index 0000000000..6f03d3ee99
--- /dev/null
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CycleCollectedJSRuntime_h
+#define mozilla_CycleCollectedJSRuntime_h
+
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DeferredFinalize.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SegmentedVector.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/TypeDecls.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsStringFwd.h"
+#include "nsTHashSet.h"
+
+class nsCycleCollectionNoteRootCallback;
+class nsIException;
+class nsWrapperCache;
+
+namespace mozilla {
+
+class JSGCThingParticipant : public nsCycleCollectionParticipant {
+ public:
+ constexpr JSGCThingParticipant() : nsCycleCollectionParticipant(false) {}
+
+ NS_IMETHOD_(void) Root(void*) override {
+ MOZ_ASSERT(false, "Don't call Root on GC things");
+ }
+
+ NS_IMETHOD_(void) Unlink(void*) override {
+ MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead");
+ }
+
+ NS_IMETHOD_(void) Unroot(void*) override {
+ MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead");
+ }
+
+ NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) override {
+ MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing");
+ }
+
+ NS_IMETHOD TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant)
+};
+
+class JSZoneParticipant : public nsCycleCollectionParticipant {
+ public:
+ constexpr JSZoneParticipant() : nsCycleCollectionParticipant(false) {}
+
+ NS_IMETHOD_(void) Root(void*) override {
+ MOZ_ASSERT(false, "Don't call Root on GC things");
+ }
+
+ NS_IMETHOD_(void) Unlink(void*) override {
+ MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead");
+ }
+
+ NS_IMETHOD_(void) Unroot(void*) override {
+ MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead");
+ }
+
+ NS_IMETHOD_(void) DeleteCycleCollectable(void*) override {
+ MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing");
+ }
+
+ NS_IMETHOD TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant)
+};
+
+class IncrementalFinalizeRunnable;
+
+// A map from JS holders to tracer objects, where the values are stored in
+// SegmentedVector to speed up iteration.
+class JSHolderMap {
+ public:
+ enum WhichHolders { AllHolders, HoldersRequiredForGrayMarking };
+
+ class Iter;
+
+ JSHolderMap();
+ ~JSHolderMap() { MOZ_RELEASE_ASSERT(!mHasIterator); }
+
+ bool Has(void* aHolder) const;
+ nsScriptObjectTracer* Get(void* aHolder) const;
+ nsScriptObjectTracer* Extract(void* aHolder);
+ void Put(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ struct Entry {
+ void* mHolder;
+ nsScriptObjectTracer* mTracer;
+#ifdef DEBUG
+ JS::Zone* mZone;
+#endif
+
+ Entry();
+ Entry(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone);
+ };
+
+ using EntryMap = mozilla::HashMap<void*, Entry*, DefaultHasher<void*>,
+ InfallibleAllocPolicy>;
+
+ using EntryVector = SegmentedVector<Entry, 256, InfallibleAllocPolicy>;
+
+ using EntryVectorMap =
+ mozilla::HashMap<JS::Zone*, UniquePtr<EntryVector>,
+ DefaultHasher<JS::Zone*>, InfallibleAllocPolicy>;
+
+ class EntryVectorIter;
+
+ bool RemoveEntry(EntryVector& aJSHolders, Entry* aEntry);
+
+ // A map from a holder pointer to a pointer to an entry in a vector.
+ EntryMap mJSHolderMap;
+
+ // A vector of holders not associated with a particular zone or that can
+ // contain pointers to GC things in more than one zone.
+ EntryVector mAnyZoneJSHolders;
+
+ // A map from a zone to a vector of holders that only contain pointers to GC
+ // things in that zone.
+ //
+ // Currently this will only contain wrapper cache wrappers since these are the
+ // only holders to pass a zone parameter through to AddJSHolder.
+ EntryVectorMap mPerZoneJSHolders;
+
+ // Iterators can mutate the element vectors by removing stale elements. Allow
+ // at most one to exist at a time.
+ bool mHasIterator = false;
+};
+
+// An iterator over an EntryVector that skips over removed entries and removes
+// them from the map.
+class JSHolderMap::EntryVectorIter {
+ public:
+ EntryVectorIter(JSHolderMap& aMap, EntryVector& aVector)
+ : mHolderMap(aMap), mVector(aVector), mIter(aVector.Iter()) {
+ Settle();
+ }
+
+ const EntryVector& Vector() const { return mVector; }
+
+ bool Done() const { return mIter.Done(); }
+ const Entry& Get() const { return mIter.Get(); }
+ void Next() {
+ mIter.Next();
+ Settle();
+ }
+
+ operator const Entry*() const { return &Get(); }
+ const Entry* operator->() const { return &Get(); }
+
+ private:
+ void Settle();
+ friend class JSHolderMap::Iter;
+
+ JSHolderMap& mHolderMap;
+ EntryVector& mVector;
+ EntryVector::IterImpl mIter;
+};
+
+class JSHolderMap::Iter {
+ public:
+ explicit Iter(JSHolderMap& aMap, WhichHolders aWhich = AllHolders);
+
+ ~Iter() {
+ MOZ_RELEASE_ASSERT(mHolderMap.mHasIterator);
+ mHolderMap.mHasIterator = false;
+ }
+
+ bool Done() const { return mIter.Done(); }
+ const Entry& Get() const { return mIter.Get(); }
+ void Next() {
+ mIter.Next();
+ Settle();
+ }
+
+ // If the holders have been removed from the map while the iterator is live,
+ // then the iterator may point to a removed entry. Update the iterator to make
+ // sure it points to a valid entry or is done.
+ void UpdateForRemovals();
+
+ operator const Entry*() const { return &Get(); }
+ const Entry* operator->() const { return &Get(); }
+
+ JS::Zone* Zone() const { return mZone; }
+
+ private:
+ void Settle();
+
+ JSHolderMap& mHolderMap;
+ Vector<JS::Zone*, 1, InfallibleAllocPolicy> mZones;
+ JS::Zone* mZone = nullptr;
+ EntryVectorIter mIter;
+};
+
+class CycleCollectedJSRuntime {
+ friend class JSGCThingParticipant;
+ friend class JSZoneParticipant;
+ friend class IncrementalFinalizeRunnable;
+ friend class CycleCollectedJSContext;
+
+ protected:
+ CycleCollectedJSRuntime(JSContext* aMainContext);
+ virtual ~CycleCollectedJSRuntime();
+
+ virtual void Shutdown(JSContext* cx);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ void UnmarkSkippableJSHolders();
+
+ virtual void TraverseAdditionalNativeRoots(
+ nsCycleCollectionNoteRootCallback& aCb) {}
+ virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) {}
+
+ virtual void CustomGCCallback(JSGCStatus aStatus) {}
+ virtual void CustomOutOfMemoryCallback() {}
+
+ CycleCollectedJSContext* GetContext() { return mContext; }
+
+ private:
+ void DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp,
+ char (&aName)[72]) const {
+ return false; // We did nothing.
+ }
+
+ void NoteGCThingJSChildren(JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ void NoteGCThingXPCOMChildren(const JSClass* aClasp, JSObject* aObj,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ virtual bool NoteCustomGCThingXPCOMChildren(
+ const JSClass* aClasp, JSObject* aObj,
+ nsCycleCollectionTraversalCallback& aCb) const {
+ return false; // We did nothing.
+ }
+
+ enum TraverseSelect { TRAVERSE_CPP, TRAVERSE_FULL };
+
+ void TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb);
+
+ void TraverseZone(JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb);
+
+ static void TraverseObjectShim(void* aData, JS::GCCellPtr aThing,
+ const JS::AutoRequireNoGC& nogc);
+
+ void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb);
+
+ static void TraceBlackJS(JSTracer* aTracer, void* aData);
+
+ // Trace gray JS roots until budget is exceeded and return whether we
+ // finished.
+ static bool TraceGrayJS(JSTracer* aTracer, js::SliceBudget& budget,
+ void* aData);
+
+ static void GCCallback(JSContext* aContext, JSGCStatus aStatus,
+ JS::GCReason aReason, void* aData);
+ static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress,
+ const JS::GCDescription& aDesc);
+ static void GCNurseryCollectionCallback(JSContext* aContext,
+ JS::GCNurseryProgress aProgress,
+ JS::GCReason aReason, void* data);
+ static void OutOfMemoryCallback(JSContext* aContext, void* aData);
+
+ static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData);
+
+ static void* BeforeWaitCallback(uint8_t* aMemory);
+ static void AfterWaitCallback(void* aCookie);
+
+ virtual void TraceNativeBlackRoots(JSTracer* aTracer){};
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ void TraceAllNativeGrayRoots(JSTracer* aTracer);
+#endif
+
+ bool TraceNativeGrayRoots(JSTracer* aTracer, JSHolderMap::WhichHolders aWhich,
+ js::SliceBudget& aBudget);
+ bool TraceJSHolders(JSTracer* aTracer, JSHolderMap::Iter& aIter,
+ js::SliceBudget& aBudget);
+
+ public:
+ enum DeferredFinalizeType {
+ // Never finalize immediately, because it would be unsafe.
+ FinalizeLater,
+ // Finalize later if we can, but it is okay to do it immediately.
+ FinalizeIncrementally,
+ // Finalize immediately, for shutdown or testing purposes.
+ FinalizeNow,
+ };
+
+ void FinalizeDeferredThings(DeferredFinalizeType aType);
+
+ virtual void PrepareForForgetSkippable() = 0;
+ virtual void BeginCycleCollectionCallback(mozilla::CCReason aReason) = 0;
+ virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0;
+ virtual void DispatchDeferredDeletion(bool aContinuation,
+ bool aPurge = false) = 0;
+
+ // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in
+ // crash reports. Here are the values that can appear in the reports:
+ enum class OOMState : uint32_t {
+ // The condition has never happened. No entry appears in the crash report.
+ OK,
+
+ // We are currently reporting the given condition.
+ //
+ // Suppose a crash report contains "JSLargeAllocationFailure:
+ // Reporting". This means we crashed while executing memory-pressure
+ // observers, trying to shake loose some memory. The large allocation in
+ // question did not return null: it is still on the stack. Had we not
+ // crashed, it would have been retried.
+ Reporting,
+
+ // The condition has been reported since the last GC.
+ //
+ // If a crash report contains "JSOutOfMemory: Reported", that means a small
+ // allocation failed, and then we crashed, probably due to buggy
+ // error-handling code that ran after allocation returned null.
+ //
+ // This contrasts with "Reporting" which means that no error-handling code
+ // had executed yet.
+ Reported,
+
+ // The condition has happened, but a GC cycle ended since then.
+ //
+ // GC is taken as a proxy for "we've been banging on the heap a good bit
+ // now and haven't crashed; the OOM was probably handled correctly".
+ Recovered
+ };
+
+ const char* OOMStateToString(const OOMState aOomState) const;
+
+ // Returns true if OOM was reported and a new successful GC cycle hasn't
+ // occurred since.
+ bool OOMReported();
+
+ void SetLargeAllocationFailure(OOMState aNewState);
+
+ void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState);
+ void OnGC(JSContext* aContext, JSGCStatus aStatus, JS::GCReason aReason);
+ void OnOutOfMemory();
+ void OnLargeAllocationFailure();
+
+ JSRuntime* Runtime() { return mJSRuntime; }
+ const JSRuntime* Runtime() const { return mJSRuntime; }
+
+ bool HasPendingIdleGCTask() const {
+ // Idle GC task associates with JSRuntime.
+ MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime());
+ return mHasPendingIdleGCTask;
+ }
+ void SetPendingIdleGCTask() {
+ // Idle GC task associates with JSRuntime.
+ MOZ_ASSERT(Runtime());
+ mHasPendingIdleGCTask = true;
+ }
+ void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; }
+
+ void RunIdleTimeGCTask() {
+ if (HasPendingIdleGCTask()) {
+ JS::MaybeRunNurseryCollection(Runtime(),
+ JS::GCReason::EAGER_NURSERY_COLLECTION);
+ ClearPendingIdleGCTask();
+ }
+ }
+
+ bool IsIdleGCTaskNeeded() {
+ return !HasPendingIdleGCTask() && Runtime() &&
+ JS::WantEagerMinorGC(Runtime()) != JS::GCReason::NO_REASON;
+ }
+
+ public:
+ void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone);
+ void RemoveJSHolder(void* aHolder);
+#ifdef DEBUG
+ void AssertNoObjectsToTrace(void* aPossibleJSHolder);
+#endif
+
+ nsCycleCollectionParticipant* GCThingParticipant();
+ nsCycleCollectionParticipant* ZoneParticipant();
+
+ nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb);
+ virtual bool UsefulToMergeZones() const;
+ void FixWeakMappingGrayBits() const;
+ void CheckGrayBits() const;
+ bool AreGCGrayBitsValid() const;
+ void GarbageCollect(JS::GCOptions options, JS::GCReason aReason) const;
+
+ // This needs to be an nsWrapperCache, not a JSObject, because we need to know
+ // when our object gets moved. But we can't trace it (and hence update our
+ // storage), because we do not want to keep it alive. nsWrapperCache handles
+ // this for us via its "object moved" handling.
+ void NurseryWrapperAdded(nsWrapperCache* aCache);
+ void JSObjectsTenured();
+
+ void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
+ DeferredFinalizeFunction aFunc, void* aThing);
+ void DeferredFinalize(nsISupports* aSupports);
+
+ void DumpJSHeap(FILE* aFile);
+
+ // Add aZone to the set of zones waiting for a GC.
+ void AddZoneWaitingForGC(JS::Zone* aZone) {
+ mZonesWaitingForGC.Insert(aZone);
+ }
+
+ static void OnZoneDestroyed(JS::GCContext* aGcx, JS::Zone* aZone);
+
+ // Prepare any zones for GC that have been passed to AddZoneWaitingForGC()
+ // since the last GC or since the last call to PrepareWaitingZonesForGC(),
+ // whichever was most recent. If there were no such zones, prepare for a
+ // full GC.
+ void PrepareWaitingZonesForGC();
+
+ // Get the current thread's CycleCollectedJSRuntime. Returns null if there
+ // isn't one.
+ static CycleCollectedJSRuntime* Get();
+
+ void SetContext(CycleCollectedJSContext* aContext);
+
+#ifdef NIGHTLY_BUILD
+ bool GetRecentDevError(JSContext* aContext,
+ JS::MutableHandle<JS::Value> aError);
+ void ClearRecentDevError();
+#endif // defined(NIGHTLY_BUILD)
+
+ private:
+ CycleCollectedJSContext* mContext;
+
+ JSGCThingParticipant mGCThingCycleCollectorGlobal;
+
+ JSZoneParticipant mJSZoneCycleCollectorGlobal;
+
+ JSRuntime* mJSRuntime;
+ bool mHasPendingIdleGCTask;
+
+ JS::GCSliceCallback mPrevGCSliceCallback;
+
+ mozilla::TimeStamp mLatestNurseryCollectionStart;
+
+ JSHolderMap mJSHolders;
+ Maybe<JSHolderMap::Iter> mHolderIter;
+
+ using DeferredFinalizerTable = nsTHashMap<DeferredFinalizeFunction, void*>;
+ DeferredFinalizerTable mDeferredFinalizerTable;
+
+ RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
+
+ OOMState mOutOfMemoryState;
+ OOMState mLargeAllocationFailureState;
+
+ static const size_t kSegmentSize = 512;
+ SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy>
+ mNurseryObjects;
+
+ nsTHashSet<JS::Zone*> mZonesWaitingForGC;
+
+ struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer {
+ void invoke(JS::Handle<JSObject*> global, Closure& closure) override;
+ };
+ EnvironmentPreparer mEnvironmentPreparer;
+
+#ifdef DEBUG
+ bool mShutdownCalled;
+#endif
+
+#ifdef NIGHTLY_BUILD
+ // Implementation of the error interceptor.
+ // Built on nightly only to avoid any possible performance impact on release
+
+ struct ErrorInterceptor final : public JSErrorInterceptor {
+ virtual void interceptError(JSContext* cx,
+ JS::Handle<JS::Value> exn) override;
+ void Shutdown(JSRuntime* rt);
+
+ // Copy of the details of the exception.
+ // We store this rather than the exception itself to avoid dealing with
+ // complicated garbage-collection scenarios, e.g. a JSContext being killed
+ // while we still hold onto an exception thrown from it.
+ struct ErrorDetails {
+ nsString mFilename;
+ nsString mMessage;
+ nsString mStack;
+ JSExnType mType;
+ uint32_t mLine;
+ uint32_t mColumn;
+ };
+
+ // If we have encountered at least one developer error,
+ // the first error we have encountered. Otherwise, or
+ // if we have reset since the latest error, `None`.
+ Maybe<ErrorDetails> mThrownError;
+ };
+ ErrorInterceptor mErrorInterceptor;
+
+#endif // defined(NIGHTLY_BUILD)
+};
+
+void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);
+
+} // namespace mozilla
+
+#endif // mozilla_CycleCollectedJSRuntime_h
diff --git a/xpcom/base/Debug.cpp b/xpcom/base/Debug.cpp
new file mode 100644
index 0000000000..7e2a4c1d10
--- /dev/null
+++ b/xpcom/base/Debug.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Debug.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+#ifdef XP_WIN
+
+void mozilla::PrintToDebugger(const char* aStr) {
+ if (::IsDebuggerPresent()) {
+ ::OutputDebugStringA(aStr);
+ }
+}
+
+#endif
diff --git a/xpcom/base/Debug.h b/xpcom/base/Debug.h
new file mode 100644
index 0000000000..1d6eaaf9a4
--- /dev/null
+++ b/xpcom/base/Debug.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Debug_h__
+#define mozilla_Debug_h__
+
+namespace mozilla {
+
+#ifdef XP_WIN
+
+// Print aStr to a debugger if the debugger is attached.
+void PrintToDebugger(const char* aStr);
+
+#endif
+
+} // namespace mozilla
+
+#endif // mozilla_Debug_h__
diff --git a/xpcom/base/DebuggerOnGCRunnable.cpp b/xpcom/base/DebuggerOnGCRunnable.cpp
new file mode 100644
index 0000000000..f8415dc3d8
--- /dev/null
+++ b/xpcom/base/DebuggerOnGCRunnable.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebuggerOnGCRunnable.h"
+
+#include <utility>
+
+#include "js/Debug.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/SchedulerGroup.h"
+
+namespace mozilla {
+
+/* static */
+nsresult DebuggerOnGCRunnable::Enqueue(JSContext* aCx,
+ const JS::GCDescription& aDesc) {
+ auto gcEvent = aDesc.toGCEvent(aCx);
+ if (!gcEvent) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<DebuggerOnGCRunnable> runOnGC =
+ new DebuggerOnGCRunnable(std::move(gcEvent));
+ return NS_DispatchToCurrentThread(runOnGC);
+}
+
+NS_IMETHODIMP
+DebuggerOnGCRunnable::Run() {
+ dom::AutoJSAPI jsapi;
+ jsapi.Init();
+ if (!JS::dbg::FireOnGarbageCollectionHook(jsapi.cx(), std::move(mGCData))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult DebuggerOnGCRunnable::Cancel() {
+ mGCData = nullptr;
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/DebuggerOnGCRunnable.h b/xpcom/base/DebuggerOnGCRunnable.h
new file mode 100644
index 0000000000..93ac48b56c
--- /dev/null
+++ b/xpcom/base/DebuggerOnGCRunnable.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DebuggerOnGCRunnable_h
+#define mozilla_DebuggerOnGCRunnable_h
+
+#include <utility>
+
+#include "js/GCAPI.h"
+#include "mozilla/UniquePtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+// Runnable to fire the SpiderMonkey Debugger API's onGarbageCollection hook.
+class DebuggerOnGCRunnable : public CancelableRunnable {
+ JS::dbg::GarbageCollectionEvent::Ptr mGCData;
+
+ explicit DebuggerOnGCRunnable(JS::dbg::GarbageCollectionEvent::Ptr&& aGCData)
+ : CancelableRunnable("DebuggerOnGCRunnable"),
+ mGCData(std::move(aGCData)) {}
+
+ public:
+ static nsresult Enqueue(JSContext* aCx, const JS::GCDescription& aDesc);
+
+ NS_DECL_NSIRUNNABLE
+ nsresult Cancel() override;
+};
+
+} // namespace mozilla
+
+#endif // ifdef mozilla_dom_DebuggerOnGCRunnable_h
diff --git a/xpcom/base/DeferredFinalize.cpp b/xpcom/base/DeferredFinalize.cpp
new file mode 100644
index 0000000000..aea034dbf3
--- /dev/null
+++ b/xpcom/base/DeferredFinalize.cpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DeferredFinalize.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+
+void mozilla::DeferredFinalize(nsISupports* aSupports) {
+ CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
+ rt->DeferredFinalize(aSupports);
+}
+
+void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
+ DeferredFinalizeFunction aFunc, void* aThing) {
+ CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
+ rt->DeferredFinalize(aAppendFunc, aFunc, aThing);
+}
diff --git a/xpcom/base/DeferredFinalize.h b/xpcom/base/DeferredFinalize.h
new file mode 100644
index 0000000000..8b0e7e28be
--- /dev/null
+++ b/xpcom/base/DeferredFinalize.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DeferredFinalize_h
+#define mozilla_DeferredFinalize_h
+
+#include <cstdint>
+#include "mozilla/Attributes.h"
+
+class nsISupports;
+
+namespace mozilla {
+
+// Called back from DeferredFinalize. Should add 'thing' to the array of smart
+// pointers in 'pointers', creating the array if 'pointers' is null, and return
+// the array.
+typedef void* (*DeferredFinalizeAppendFunction)(void* aPointers, void* aThing);
+
+// Called to finalize a number of objects. Slice is the number of objects to
+// finalize. The return value indicates whether it finalized all objects in the
+// buffer. If it returns true, the function will not be called again, so the
+// function should free aData.
+typedef bool (*DeferredFinalizeFunction)(uint32_t aSlice, void* aData);
+
+void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
+ DeferredFinalizeFunction aFunc, void* aThing);
+
+MOZ_NEVER_INLINE void DeferredFinalize(nsISupports* aSupports);
+
+} // namespace mozilla
+
+#endif // mozilla_DeferredFinalize_h
diff --git a/xpcom/base/EnumeratedArrayCycleCollection.h b/xpcom/base/EnumeratedArrayCycleCollection.h
new file mode 100644
index 0000000000..465d4cf38a
--- /dev/null
+++ b/xpcom/base/EnumeratedArrayCycleCollection.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EnumeratedArrayCycleCollection_h_
+#define EnumeratedArrayCycleCollection_h_
+
+#include "mozilla/EnumeratedArray.h"
+#include "nsCycleCollectionTraversalCallback.h"
+
+template <typename IndexType, IndexType SizeAsEnumValue, typename ValueType>
+inline void ImplCycleCollectionUnlink(
+ mozilla::EnumeratedArray<IndexType, SizeAsEnumValue, ValueType>& aField) {
+ for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) {
+ aField[IndexType(i)] = nullptr;
+ }
+}
+
+template <typename IndexType, IndexType SizeAsEnumValue, typename ValueType>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ mozilla::EnumeratedArray<IndexType, SizeAsEnumValue, ValueType>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) {
+ ImplCycleCollectionTraverse(aCallback, aField[IndexType(i)], aName, aFlags);
+ }
+}
+
+#endif // EnumeratedArrayCycleCollection_h_
diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py
new file mode 100755
index 0000000000..4446dc9cc0
--- /dev/null
+++ b/xpcom/base/ErrorList.py
@@ -0,0 +1,1425 @@
+#!/usr/bin/env python
+from collections import OrderedDict
+
+
+class Mod:
+ """
+ A nserror module. When used with a `with` statement, binds the itself to
+ Mod.active.
+ """
+
+ active = None
+
+ def __init__(self, num):
+ self.num = num
+
+ def __enter__(self):
+ Mod.active = self
+
+ def __exit__(self, _type, _value, _traceback):
+ Mod.active = None
+
+
+modules = OrderedDict()
+
+# To add error code to your module, you need to do the following:
+#
+# 1) Add a module offset code. Add yours to the bottom of the list
+# right below this comment, adding 1.
+#
+# 2) In your module, define a header file which uses one of the
+# NE_ERROR_GENERATExxxxxx macros. Some examples below:
+#
+# #define NS_ERROR_MYMODULE_MYERROR1 \
+# NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR,NS_ERROR_MODULE_MYMODULE,1)
+# #define NS_ERROR_MYMODULE_MYERROR2 NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_MYMODULE,2)
+# #define NS_ERROR_MYMODULE_MYERROR3 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_MYMODULE,3)
+
+# @name Standard Module Offset Code. Each Module should identify a unique number
+# and then all errors associated with that module become offsets from the
+# base associated with that module id. There are 16 bits of code bits for
+# each module.
+
+modules["XPCOM"] = Mod(1)
+modules["BASE"] = Mod(2)
+modules["GFX"] = Mod(3)
+modules["WIDGET"] = Mod(4)
+modules["CALENDAR"] = Mod(5)
+modules["NETWORK"] = Mod(6)
+modules["PLUGINS"] = Mod(7)
+modules["LAYOUT"] = Mod(8)
+modules["HTMLPARSER"] = Mod(9)
+modules["RDF"] = Mod(10)
+modules["UCONV"] = Mod(11)
+modules["REG"] = Mod(12)
+modules["FILES"] = Mod(13)
+modules["DOM"] = Mod(14)
+modules["IMGLIB"] = Mod(15)
+modules["MAILNEWS"] = Mod(16)
+modules["EDITOR"] = Mod(17)
+modules["XPCONNECT"] = Mod(18)
+modules["PROFILE"] = Mod(19)
+modules["LDAP"] = Mod(20)
+modules["SECURITY"] = Mod(21)
+modules["DOM_XPATH"] = Mod(22)
+# Mod(23) used to be NS_ERROR_MODULE_DOM_RANGE (see bug 711047)
+modules["URILOADER"] = Mod(24)
+modules["CONTENT"] = Mod(25)
+modules["PYXPCOM"] = Mod(26)
+modules["XSLT"] = Mod(27)
+modules["IPC"] = Mod(28)
+modules["SVG"] = Mod(29)
+modules["STORAGE"] = Mod(30)
+modules["SCHEMA"] = Mod(31)
+modules["DOM_FILE"] = Mod(32)
+modules["DOM_INDEXEDDB"] = Mod(33)
+modules["DOM_FILEHANDLE"] = Mod(34)
+modules["SIGNED_JAR"] = Mod(35)
+modules["DOM_FILESYSTEM"] = Mod(36)
+modules["DOM_BLUETOOTH"] = Mod(37)
+modules["SIGNED_APP"] = Mod(38)
+modules["DOM_ANIM"] = Mod(39)
+modules["DOM_PUSH"] = Mod(40)
+modules["DOM_MEDIA"] = Mod(41)
+modules["URL_CLASSIFIER"] = Mod(42)
+# ErrorResult gets its own module to reduce the chance of someone accidentally
+# defining an error code matching one of the ErrorResult ones.
+modules["ERRORRESULT"] = Mod(43)
+# Win32 system error codes, which are not mapped to a specific other value,
+# see Bug 1686041.
+modules["WIN32"] = Mod(44)
+modules["WDBA"] = Mod(45)
+
+# NS_ERROR_MODULE_GENERAL should be used by modules that do not
+# care if return code values overlap. Callers of methods that
+# return such codes should be aware that they are not
+# globally unique. Implementors should be careful about blindly
+# returning codes from other modules that might also use
+# the generic base.
+modules["GENERAL"] = Mod(51)
+
+MODULE_BASE_OFFSET = 0x45
+
+NS_ERROR_SEVERITY_SUCCESS = 0
+NS_ERROR_SEVERITY_ERROR = 1
+
+
+def SUCCESS_OR_FAILURE(sev, module, code):
+ return (sev << 31) | ((module + MODULE_BASE_OFFSET) << 16) | code
+
+
+def FAILURE(code):
+ return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_ERROR, Mod.active.num, code)
+
+
+def SUCCESS(code):
+ return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_SUCCESS, Mod.active.num, code)
+
+
+# Errors is an ordered dictionary, so that we can recover the order in which
+# they were defined. This is important for determining which name is the
+# canonical name for an error code.
+errors = OrderedDict()
+
+# Standard "it worked" return value
+errors["NS_OK"] = 0
+
+# =======================================================================
+# Core errors, not part of any modules
+# =======================================================================
+errors["NS_ERROR_BASE"] = 0xC1F30000
+# Returned when an instance is not initialized
+errors["NS_ERROR_NOT_INITIALIZED"] = errors["NS_ERROR_BASE"] + 1
+# Returned when an instance is already initialized
+errors["NS_ERROR_ALREADY_INITIALIZED"] = errors["NS_ERROR_BASE"] + 2
+# Returned by a not implemented function
+errors["NS_ERROR_NOT_IMPLEMENTED"] = 0x80004001
+# Returned when a given interface is not supported.
+errors["NS_NOINTERFACE"] = 0x80004002
+errors["NS_ERROR_NO_INTERFACE"] = errors["NS_NOINTERFACE"]
+# Returned when a function aborts
+errors["NS_ERROR_ABORT"] = 0x80004004
+# Returned when a function fails
+errors["NS_ERROR_FAILURE"] = 0x80004005
+# Returned when an unexpected error occurs
+errors["NS_ERROR_UNEXPECTED"] = 0x8000FFFF
+# Returned when a memory allocation fails
+errors["NS_ERROR_OUT_OF_MEMORY"] = 0x8007000E
+# Returned when an illegal value is passed
+errors["NS_ERROR_ILLEGAL_VALUE"] = 0x80070057
+errors["NS_ERROR_INVALID_ARG"] = errors["NS_ERROR_ILLEGAL_VALUE"]
+errors["NS_ERROR_INVALID_POINTER"] = errors["NS_ERROR_INVALID_ARG"]
+errors["NS_ERROR_NULL_POINTER"] = errors["NS_ERROR_INVALID_ARG"]
+# Returned when an operation can't complete due to an unavailable resource
+errors["NS_ERROR_NOT_AVAILABLE"] = 0x80040111
+# Returned when a class is not registered
+errors["NS_ERROR_FACTORY_NOT_REGISTERED"] = 0x80040154
+# Returned when a class cannot be registered, but may be tried again later
+errors["NS_ERROR_FACTORY_REGISTER_AGAIN"] = 0x80040155
+# Returned when a dynamically loaded factory couldn't be found
+errors["NS_ERROR_FACTORY_NOT_LOADED"] = 0x800401F8
+# Returned when a factory doesn't support signatures
+errors["NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT"] = errors["NS_ERROR_BASE"] + 0x101
+# Returned when a factory already is registered
+errors["NS_ERROR_FACTORY_EXISTS"] = errors["NS_ERROR_BASE"] + 0x100
+
+
+# =======================================================================
+# 1: NS_ERROR_MODULE_XPCOM
+# =======================================================================
+with modules["XPCOM"]:
+ # Result codes used by nsIVariant
+ errors["NS_ERROR_CANNOT_CONVERT_DATA"] = FAILURE(1)
+ errors["NS_ERROR_OBJECT_IS_IMMUTABLE"] = FAILURE(2)
+ errors["NS_ERROR_LOSS_OF_SIGNIFICANT_DATA"] = FAILURE(3)
+ # Result code used by nsIThreadManager
+ errors["NS_ERROR_NOT_SAME_THREAD"] = FAILURE(4)
+ # Various operations are not permitted during XPCOM shutdown and will fail
+ # with this exception.
+ errors["NS_ERROR_ILLEGAL_DURING_SHUTDOWN"] = FAILURE(30)
+ errors["NS_ERROR_SERVICE_NOT_AVAILABLE"] = FAILURE(22)
+
+ errors["NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA"] = SUCCESS(1)
+ # Used by nsCycleCollectionParticipant
+ errors["NS_SUCCESS_INTERRUPTED_TRAVERSE"] = SUCCESS(2)
+
+# =======================================================================
+# 2: NS_ERROR_MODULE_BASE
+# =======================================================================
+with modules["BASE"]:
+ # I/O Errors
+
+ # Stream closed
+ errors["NS_BASE_STREAM_CLOSED"] = FAILURE(2)
+ # Error from the operating system
+ errors["NS_BASE_STREAM_OSERROR"] = FAILURE(3)
+ # Illegal arguments
+ errors["NS_BASE_STREAM_ILLEGAL_ARGS"] = FAILURE(4)
+ # For unichar streams
+ errors["NS_BASE_STREAM_NO_CONVERTER"] = FAILURE(5)
+ # For unichar streams
+ errors["NS_BASE_STREAM_BAD_CONVERSION"] = FAILURE(6)
+ errors["NS_BASE_STREAM_WOULD_BLOCK"] = FAILURE(7)
+
+
+# =======================================================================
+# 3: NS_ERROR_MODULE_GFX
+# =======================================================================
+with modules["GFX"]:
+ # no printer available (e.g. cannot find _any_ printer)
+ errors["NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE"] = FAILURE(1)
+ # _specified_ (by name) printer not found
+ errors["NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND"] = FAILURE(2)
+ # print-to-file: could not open output file
+ errors["NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE"] = FAILURE(3)
+ # print: starting document
+ errors["NS_ERROR_GFX_PRINTER_STARTDOC"] = FAILURE(4)
+ # print: ending document
+ errors["NS_ERROR_GFX_PRINTER_ENDDOC"] = FAILURE(5)
+ # print: starting page
+ errors["NS_ERROR_GFX_PRINTER_STARTPAGE"] = FAILURE(6)
+ # The document is still being loaded
+ errors["NS_ERROR_GFX_PRINTER_DOC_IS_BUSY"] = FAILURE(7)
+
+ # Font cmap is strangely structured - avoid this font!
+ errors["NS_ERROR_GFX_CMAP_MALFORMED"] = FAILURE(51)
+
+
+# =======================================================================
+# 4: NS_ERROR_MODULE_WIDGET
+# =======================================================================
+with modules["WIDGET"]:
+ # Used by:
+ # - nsIWidget::NotifyIME()
+ # Returned when the notification or the event is handled and it's consumed
+ # by somebody.
+ errors["NS_SUCCESS_EVENT_CONSUMED"] = SUCCESS(1)
+
+
+# =======================================================================
+# 6: NS_ERROR_MODULE_NETWORK
+# =======================================================================
+with modules["NETWORK"]:
+ # General async request error codes:
+ #
+ # These error codes are commonly passed through callback methods to indicate
+ # the status of some requested async request.
+ #
+ # For example, see nsIRequestObserver::onStopRequest.
+
+ # The async request completed successfully.
+ errors["NS_BINDING_SUCCEEDED"] = errors["NS_OK"]
+
+ # The async request failed for some unknown reason.
+ errors["NS_BINDING_FAILED"] = FAILURE(1)
+ # The async request failed because it was aborted by some user action.
+ errors["NS_BINDING_ABORTED"] = FAILURE(2)
+ # The async request has been "redirected" to a different async request.
+ # (e.g., an HTTP redirect occurred).
+ #
+ # This error code is used with load groups to notify the load group observer
+ # when a request in the load group is redirected to another request.
+ errors["NS_BINDING_REDIRECTED"] = FAILURE(3)
+ # The async request has been "retargeted" to a different "handler."
+ #
+ # This error code is used with load groups to notify the load group observer
+ # when a request in the load group is removed from the load group and added
+ # to a different load group.
+ errors["NS_BINDING_RETARGETED"] = FAILURE(4)
+
+ # Miscellaneous error codes: These errors are not typically passed via
+ # onStopRequest.
+
+ # The URI is malformed.
+ errors["NS_ERROR_MALFORMED_URI"] = FAILURE(10)
+ # The requested action could not be completed while the object is busy.
+ # Implementations of nsIChannel::asyncOpen will commonly return this error
+ # if the channel has already been opened (and has not yet been closed).
+ errors["NS_ERROR_IN_PROGRESS"] = FAILURE(15)
+ # Returned from nsIChannel::asyncOpen to indicate that OnDataAvailable will
+ # not be called because there is no content available. This is used by
+ # helper app style protocols (e.g., mailto). XXX perhaps this should be a
+ # success code.
+ errors["NS_ERROR_NO_CONTENT"] = FAILURE(17)
+ # The URI scheme corresponds to an unknown protocol handler.
+ errors["NS_ERROR_UNKNOWN_PROTOCOL"] = FAILURE(18)
+ # The content encoding of the source document was incorrect, for example
+ # returning a plain HTML document advertised as Content-Encoding: gzip
+ errors["NS_ERROR_INVALID_CONTENT_ENCODING"] = FAILURE(27)
+ # A transport level corruption was found in the source document. for example
+ # a document with a calculated checksum that does not match the Content-MD5
+ # http header.
+ errors["NS_ERROR_CORRUPTED_CONTENT"] = FAILURE(29)
+ # A content signature verification failed for some reason. This can be either
+ # an actual verification error, or any other error that led to the fact that
+ # a content signature that was expected couldn't be verified.
+ errors["NS_ERROR_INVALID_SIGNATURE"] = FAILURE(58)
+ # While parsing for the first component of a header field using syntax as in
+ # Content-Disposition or Content-Type, the first component was found to be
+ # empty, such as in: Content-Disposition: ; filename=foo
+ errors["NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY"] = FAILURE(34)
+ # Returned from nsIChannel::asyncOpen when trying to open the channel again
+ # (reopening is not supported).
+ errors["NS_ERROR_ALREADY_OPENED"] = FAILURE(73)
+
+ # Connectivity error codes:
+
+ # The connection is already established. XXX unused - consider removing.
+ errors["NS_ERROR_ALREADY_CONNECTED"] = FAILURE(11)
+ # The connection does not exist. XXX unused - consider removing.
+ errors["NS_ERROR_NOT_CONNECTED"] = FAILURE(12)
+ # The connection attempt failed, for example, because no server was
+ # listening at specified host:port.
+ errors["NS_ERROR_CONNECTION_REFUSED"] = FAILURE(13)
+ # The connection was lost due to a timeout error.
+ errors["NS_ERROR_NET_TIMEOUT"] = FAILURE(14)
+ # The requested action could not be completed while the networking library
+ # is in the offline state.
+ errors["NS_ERROR_OFFLINE"] = FAILURE(16)
+ # The requested action was prohibited because it would have caused the
+ # networking library to establish a connection to an unsafe or otherwise
+ # banned port.
+ errors["NS_ERROR_PORT_ACCESS_NOT_ALLOWED"] = FAILURE(19)
+ # The connection was established, but no data was ever received.
+ errors["NS_ERROR_NET_RESET"] = FAILURE(20)
+ # The connection was established, but the data transfer was interrupted.
+ errors["NS_ERROR_NET_INTERRUPT"] = FAILURE(71)
+ # The connection attempt to a proxy failed.
+ errors["NS_ERROR_PROXY_CONNECTION_REFUSED"] = FAILURE(72)
+ # A transfer was only partially done when it completed.
+ errors["NS_ERROR_NET_PARTIAL_TRANSFER"] = FAILURE(76)
+ # HTTP/2 detected invalid TLS configuration
+ errors["NS_ERROR_NET_INADEQUATE_SECURITY"] = FAILURE(82)
+ # HTTP/2 sent a GOAWAY
+ errors["NS_ERROR_NET_HTTP2_SENT_GOAWAY"] = FAILURE(83)
+ # HTTP/3 protocol internal error
+ errors["NS_ERROR_NET_HTTP3_PROTOCOL_ERROR"] = FAILURE(84)
+ # A timeout error code that can be used to cancel requests.
+ errors["NS_ERROR_NET_TIMEOUT_EXTERNAL"] = FAILURE(85)
+ # An error related to HTTPS-only mode
+ errors["NS_ERROR_HTTPS_ONLY"] = FAILURE(86)
+ # A WebSocket connection is failed.
+ errors["NS_ERROR_WEBSOCKET_CONNECTION_REFUSED"] = FAILURE(87)
+ # A connection to a non local address is refused because
+ # xpc::AreNonLocalConnectionsDisabled() returns true.
+ errors["NS_ERROR_NON_LOCAL_CONNECTION_REFUSED"] = FAILURE(88)
+ # Connection to a sts host without a hsts header.
+ errors["NS_ERROR_BAD_HSTS_CERT"] = FAILURE(89)
+ # Error parsing the status line of an HTTP response
+ errors["NS_ERROR_PARSING_HTTP_STATUS_LINE"] = FAILURE(90)
+ # The user refused to navigate to a potentially unsafe URL with
+ # embedded credentials/superfluos authentication.
+ errors["NS_ERROR_SUPERFLUOS_AUTH"] = FAILURE(91)
+
+ # XXX really need to better rationalize these error codes. are consumers of
+ # necko really expected to know how to discern the meaning of these??
+ # This request is not resumable, but it was tried to resume it, or to
+ # request resume-specific data.
+ errors["NS_ERROR_NOT_RESUMABLE"] = FAILURE(25)
+ # The request failed as a result of a detected redirection loop.
+ errors["NS_ERROR_REDIRECT_LOOP"] = FAILURE(31)
+ # It was attempted to resume the request, but the entity has changed in the
+ # meantime.
+ errors["NS_ERROR_ENTITY_CHANGED"] = FAILURE(32)
+ # The request failed because the content type returned by the server was not
+ # a type expected by the channel (for nested channels such as the JAR
+ # channel).
+ errors["NS_ERROR_UNSAFE_CONTENT_TYPE"] = FAILURE(74)
+ # The request resulted in an error page being displayed.
+ errors["NS_ERROR_LOAD_SHOWED_ERRORPAGE"] = FAILURE(77)
+ # The request occurred in docshell that lacks a treeowner, so it is
+ # probably in the process of being torn down.
+ errors["NS_ERROR_DOCSHELL_DYING"] = FAILURE(78)
+
+ # DNS specific error codes:
+
+ # The lookup of a hostname failed. This generally refers to the hostname
+ # from the URL being loaded.
+ errors["NS_ERROR_UNKNOWN_HOST"] = FAILURE(30)
+ # A low or medium priority DNS lookup failed because the pending queue was
+ # already full. High priorty (the default) always makes room
+ errors["NS_ERROR_DNS_LOOKUP_QUEUE_FULL"] = FAILURE(33)
+ # The lookup of a proxy hostname failed. If a channel is configured to
+ # speak to a proxy server, then it will generate this error if the proxy
+ # hostname cannot be resolved.
+ errors["NS_ERROR_UNKNOWN_PROXY_HOST"] = FAILURE(42)
+ # This DNS error will occur when the resolver uses the Extended DNS Error
+ # option to indicate an error code for which we should not fall back to the
+ # default DNS resolver. This means the DNS failure is definitive.
+ errors["NS_ERROR_DEFINITIVE_UNKNOWN_HOST"] = FAILURE(43)
+
+ # Socket specific error codes:
+
+ # The specified socket type does not exist.
+ errors["NS_ERROR_UNKNOWN_SOCKET_TYPE"] = FAILURE(51)
+ # The specified socket type could not be created.
+ errors["NS_ERROR_SOCKET_CREATE_FAILED"] = FAILURE(52)
+ # The operating system doesn't support the given type of address.
+ errors["NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"] = FAILURE(53)
+ # The address to which we tried to bind the socket was busy.
+ errors["NS_ERROR_SOCKET_ADDRESS_IN_USE"] = FAILURE(54)
+
+ # Cache specific error codes:
+ errors["NS_ERROR_CACHE_KEY_NOT_FOUND"] = FAILURE(61)
+ errors["NS_ERROR_CACHE_DATA_IS_STREAM"] = FAILURE(62)
+ errors["NS_ERROR_CACHE_DATA_IS_NOT_STREAM"] = FAILURE(63)
+ errors["NS_ERROR_CACHE_WAIT_FOR_VALIDATION"] = FAILURE(64)
+ errors["NS_ERROR_CACHE_ENTRY_DOOMED"] = FAILURE(65)
+ errors["NS_ERROR_CACHE_READ_ACCESS_DENIED"] = FAILURE(66)
+ errors["NS_ERROR_CACHE_WRITE_ACCESS_DENIED"] = FAILURE(67)
+ errors["NS_ERROR_CACHE_IN_USE"] = FAILURE(68)
+ # Error passed through onStopRequest if the document could not be fetched
+ # from the cache.
+ errors["NS_ERROR_DOCUMENT_NOT_CACHED"] = FAILURE(70)
+
+ # Effective TLD Service specific error codes:
+
+ # The requested number of domain levels exceeds those present in the host
+ # string.
+ errors["NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS"] = FAILURE(80)
+ # The host string is an IP address.
+ errors["NS_ERROR_HOST_IS_IP_ADDRESS"] = FAILURE(81)
+
+ # StreamLoader specific result codes:
+
+ # Result code returned by nsIStreamLoaderObserver to indicate that the
+ # observer is taking over responsibility for the data buffer, and the loader
+ # should NOT free it.
+ errors["NS_SUCCESS_ADOPTED_DATA"] = SUCCESS(90)
+
+ # This success code may be returned by nsIAuthModule::getNextToken to
+ # indicate that the authentication is finished and thus there's no need
+ # to call getNextToken again.
+ errors["NS_SUCCESS_AUTH_FINISHED"] = SUCCESS(40)
+
+ # These are really not "results", they're statuses, used by nsITransport and
+ # friends. This is abuse of nsresult, but we'll put up with it for now.
+ # nsITransport
+ errors["NS_NET_STATUS_READING"] = SUCCESS(8)
+ errors["NS_NET_STATUS_WRITING"] = SUCCESS(9)
+
+ # nsISocketTransport
+ errors["NS_NET_STATUS_RESOLVING_HOST"] = SUCCESS(3)
+ errors["NS_NET_STATUS_RESOLVED_HOST"] = SUCCESS(11)
+ errors["NS_NET_STATUS_CONNECTING_TO"] = SUCCESS(7)
+ errors["NS_NET_STATUS_CONNECTED_TO"] = SUCCESS(4)
+ errors["NS_NET_STATUS_TLS_HANDSHAKE_STARTING"] = SUCCESS(12)
+ errors["NS_NET_STATUS_TLS_HANDSHAKE_ENDED"] = SUCCESS(13)
+ errors["NS_NET_STATUS_SENDING_TO"] = SUCCESS(5)
+ errors["NS_NET_STATUS_WAITING_FOR"] = SUCCESS(10)
+ errors["NS_NET_STATUS_RECEIVING_FROM"] = SUCCESS(6)
+
+ # nsIInterceptedChannel
+ # Generic error for non-specific failures during service worker interception
+ errors["NS_ERROR_INTERCEPTION_FAILED"] = FAILURE(100)
+
+ errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] = FAILURE(200)
+ errors["NS_ERROR_WEBTRANSPORT_CODE_END"] = (
+ errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] + 255
+ )
+
+ # All Http proxy CONNECT response codes
+ errors["NS_ERROR_PROXY_CODE_BASE"] = FAILURE(1000)
+ # Redirection 3xx
+ errors["NS_ERROR_PROXY_MULTIPLE_CHOICES"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 300
+ errors["NS_ERROR_PROXY_MOVED_PERMANENTLY"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 301
+ )
+ errors["NS_ERROR_PROXY_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 302
+ errors["NS_ERROR_PROXY_SEE_OTHER"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 303
+ errors["NS_ERROR_PROXY_NOT_MODIFIED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 304
+ errors["NS_ERROR_PROXY_TEMPORARY_REDIRECT"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 307
+ )
+ errors["NS_ERROR_PROXY_PERMANENT_REDIRECT"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 308
+ )
+
+ # Client error 4xx
+ errors["NS_ERROR_PROXY_BAD_REQUEST"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 400
+ errors["NS_ERROR_PROXY_UNAUTHORIZED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 401
+ errors["NS_ERROR_PROXY_PAYMENT_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 402
+ errors["NS_ERROR_PROXY_FORBIDDEN"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 403
+ errors["NS_ERROR_PROXY_NOT_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 404
+ errors["NS_ERROR_PROXY_METHOD_NOT_ALLOWED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 405
+ )
+ errors["NS_ERROR_PROXY_NOT_ACCEPTABLE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 406
+ # The proxy requires authentication; used when we can't easily propagate 407s.
+ errors["NS_ERROR_PROXY_AUTHENTICATION_FAILED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 407
+ )
+ errors["NS_ERROR_PROXY_REQUEST_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 408
+ errors["NS_ERROR_PROXY_CONFLICT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 409
+ errors["NS_ERROR_PROXY_GONE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 410
+ errors["NS_ERROR_PROXY_LENGTH_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 411
+ errors["NS_ERROR_PROXY_PRECONDITION_FAILED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 412
+ )
+ errors["NS_ERROR_PROXY_REQUEST_ENTITY_TOO_LARGE"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 413
+ )
+ errors["NS_ERROR_PROXY_REQUEST_URI_TOO_LONG"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 414
+ )
+ errors["NS_ERROR_PROXY_UNSUPPORTED_MEDIA_TYPE"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 415
+ )
+ errors["NS_ERROR_PROXY_REQUESTED_RANGE_NOT_SATISFIABLE"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 416
+ )
+ errors["NS_ERROR_PROXY_EXPECTATION_FAILED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 417
+ )
+ errors["NS_ERROR_PROXY_MISDIRECTED_REQUEST"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 421
+ )
+ errors["NS_ERROR_PROXY_TOO_EARLY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 425
+ errors["NS_ERROR_PROXY_UPGRADE_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 426
+ errors["NS_ERROR_PROXY_PRECONDITION_REQUIRED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 428
+ )
+ # Indicates that we have sent too many requests in a given amount of time.
+ errors["NS_ERROR_PROXY_TOO_MANY_REQUESTS"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 429
+ )
+ errors["NS_ERROR_PROXY_REQUEST_HEADER_FIELDS_TOO_LARGE"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 431
+ )
+ errors["NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 451
+ )
+
+ # Server error 5xx
+ errors["NS_ERROR_PROXY_INTERNAL_SERVER_ERROR"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 500
+ )
+ errors["NS_ERROR_PROXY_NOT_IMPLEMENTED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 501
+ errors["NS_ERROR_PROXY_BAD_GATEWAY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 502
+ errors["NS_ERROR_PROXY_SERVICE_UNAVAILABLE"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 503
+ )
+ # The proxy did get any response from the remote server in time.
+ errors["NS_ERROR_PROXY_GATEWAY_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 504
+ errors["NS_ERROR_PROXY_VERSION_NOT_SUPPORTED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 505
+ )
+ errors["NS_ERROR_PROXY_VARIANT_ALSO_NEGOTIATES"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 506
+ )
+ errors["NS_ERROR_PROXY_NOT_EXTENDED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 510
+ errors["NS_ERROR_PROXY_NETWORK_AUTHENTICATION_REQUIRED"] = (
+ errors["NS_ERROR_PROXY_CODE_BASE"] + 511
+ )
+
+# =======================================================================
+# 7: NS_ERROR_MODULE_PLUGINS
+# =======================================================================
+with modules["PLUGINS"]:
+ errors["NS_ERROR_PLUGINS_PLUGINSNOTCHANGED"] = FAILURE(1000)
+ errors["NS_ERROR_PLUGIN_DISABLED"] = FAILURE(1001)
+ errors["NS_ERROR_PLUGIN_BLOCKLISTED"] = FAILURE(1002)
+ errors["NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED"] = FAILURE(1003)
+ errors["NS_ERROR_PLUGIN_CLICKTOPLAY"] = FAILURE(1004)
+
+
+# =======================================================================
+# 8: NS_ERROR_MODULE_LAYOUT
+# =======================================================================
+with modules["LAYOUT"]:
+ # Return code for SheetLoadData::VerifySheetReadyToParse
+ errors["NS_OK_PARSE_SHEET"] = SUCCESS(1)
+
+
+# =======================================================================
+# 9: NS_ERROR_MODULE_HTMLPARSER
+# =======================================================================
+with modules["HTMLPARSER"]:
+ errors["NS_ERROR_HTMLPARSER_CONTINUE"] = errors["NS_OK"]
+
+ errors["NS_ERROR_HTMLPARSER_EOF"] = FAILURE(1000)
+ errors["NS_ERROR_HTMLPARSER_UNKNOWN"] = FAILURE(1001)
+ errors["NS_ERROR_HTMLPARSER_CANTPROPAGATE"] = FAILURE(1002)
+ errors["NS_ERROR_HTMLPARSER_CONTEXTMISMATCH"] = FAILURE(1003)
+ errors["NS_ERROR_HTMLPARSER_BADFILENAME"] = FAILURE(1004)
+ errors["NS_ERROR_HTMLPARSER_BADURL"] = FAILURE(1005)
+ errors["NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT"] = FAILURE(1006)
+ errors["NS_ERROR_HTMLPARSER_INTERRUPTED"] = FAILURE(1007)
+ errors["NS_ERROR_HTMLPARSER_BLOCK"] = FAILURE(1008)
+ errors["NS_ERROR_HTMLPARSER_BADTOKENIZER"] = FAILURE(1009)
+ errors["NS_ERROR_HTMLPARSER_BADATTRIBUTE"] = FAILURE(1010)
+ errors["NS_ERROR_HTMLPARSER_UNRESOLVEDDTD"] = FAILURE(1011)
+ errors["NS_ERROR_HTMLPARSER_MISPLACEDTABLECONTENT"] = FAILURE(1012)
+ errors["NS_ERROR_HTMLPARSER_BADDTD"] = FAILURE(1013)
+ errors["NS_ERROR_HTMLPARSER_BADCONTEXT"] = FAILURE(1014)
+ errors["NS_ERROR_HTMLPARSER_STOPPARSING"] = FAILURE(1015)
+ errors["NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL"] = FAILURE(1016)
+ errors["NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP"] = FAILURE(1017)
+ errors["NS_ERROR_HTMLPARSER_FAKE_ENDTAG"] = FAILURE(1018)
+ errors["NS_ERROR_HTMLPARSER_INVALID_COMMENT"] = FAILURE(1019)
+
+
+# =======================================================================
+# 10: NS_ERROR_MODULE_RDF
+# =======================================================================
+with modules["RDF"]:
+ # Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion
+ # (or unassertion was accepted by the datasource
+ errors["NS_RDF_ASSERTION_ACCEPTED"] = errors["NS_OK"]
+ # Returned from nsIRDFDataSource::GetSource() and GetTarget() if the
+ # source/target has no value
+ errors["NS_RDF_NO_VALUE"] = SUCCESS(2)
+ # Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion
+ # (or unassertion) was rejected by the datasource; i.e., the datasource was
+ # not willing to record the statement.
+ errors["NS_RDF_ASSERTION_REJECTED"] = SUCCESS(3)
+ # Return this from rdfITripleVisitor to stop cycling
+ errors["NS_RDF_STOP_VISIT"] = SUCCESS(4)
+
+
+# =======================================================================
+# 11: NS_ERROR_MODULE_UCONV
+# =======================================================================
+with modules["UCONV"]:
+ errors["NS_ERROR_UCONV_NOCONV"] = FAILURE(1)
+ errors["NS_ERROR_UDEC_ILLEGALINPUT"] = FAILURE(14)
+
+ errors["NS_OK_HAD_REPLACEMENTS"] = SUCCESS(3)
+ errors["NS_OK_UDEC_MOREINPUT"] = SUCCESS(12)
+ errors["NS_OK_UDEC_MOREOUTPUT"] = SUCCESS(13)
+ errors["NS_OK_UENC_MOREOUTPUT"] = SUCCESS(34)
+ errors["NS_ERROR_UENC_NOMAPPING"] = SUCCESS(35)
+
+ # BEGIN DEPRECATED
+ errors["NS_ERROR_ILLEGAL_INPUT"] = errors["NS_ERROR_UDEC_ILLEGALINPUT"]
+ # END DEPRECATED
+
+
+# =======================================================================
+# 13: NS_ERROR_MODULE_FILES
+# =======================================================================
+with modules["FILES"]:
+ errors["NS_ERROR_FILE_UNRECOGNIZED_PATH"] = FAILURE(1)
+ errors["NS_ERROR_FILE_UNRESOLVABLE_SYMLINK"] = FAILURE(2)
+ errors["NS_ERROR_FILE_EXECUTION_FAILED"] = FAILURE(3)
+ errors["NS_ERROR_FILE_UNKNOWN_TYPE"] = FAILURE(4)
+ errors["NS_ERROR_FILE_DESTINATION_NOT_DIR"] = FAILURE(5)
+ errors["NS_ERROR_FILE_COPY_OR_MOVE_FAILED"] = FAILURE(7)
+ errors["NS_ERROR_FILE_ALREADY_EXISTS"] = FAILURE(8)
+ errors["NS_ERROR_FILE_INVALID_PATH"] = FAILURE(9)
+ errors["NS_ERROR_FILE_CORRUPTED"] = FAILURE(11)
+ errors["NS_ERROR_FILE_NOT_DIRECTORY"] = FAILURE(12)
+ errors["NS_ERROR_FILE_IS_DIRECTORY"] = FAILURE(13)
+ errors["NS_ERROR_FILE_IS_LOCKED"] = FAILURE(14)
+ errors["NS_ERROR_FILE_TOO_BIG"] = FAILURE(15)
+ errors["NS_ERROR_FILE_NO_DEVICE_SPACE"] = FAILURE(16)
+ errors["NS_ERROR_FILE_NAME_TOO_LONG"] = FAILURE(17)
+ errors["NS_ERROR_FILE_NOT_FOUND"] = FAILURE(18)
+ errors["NS_ERROR_FILE_READ_ONLY"] = FAILURE(19)
+ errors["NS_ERROR_FILE_DIR_NOT_EMPTY"] = FAILURE(20)
+ errors["NS_ERROR_FILE_ACCESS_DENIED"] = FAILURE(21)
+ errors["NS_ERROR_FILE_FS_CORRUPTED"] = FAILURE(22)
+ errors["NS_ERROR_FILE_DEVICE_FAILURE"] = FAILURE(23)
+ errors["NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE"] = FAILURE(24)
+ errors["NS_ERROR_FILE_INVALID_HANDLE"] = FAILURE(25)
+
+ errors["NS_SUCCESS_FILE_DIRECTORY_EMPTY"] = SUCCESS(1)
+ # Result codes used by nsIDirectoryServiceProvider2
+ errors["NS_SUCCESS_AGGREGATE_RESULT"] = SUCCESS(2)
+
+
+# =======================================================================
+# 14: NS_ERROR_MODULE_DOM
+# =======================================================================
+with modules["DOM"]:
+ # XXX If you add a new DOM error code, also add an error string to
+ # dom/base/domerr.msg
+
+ # Standard DOM error codes: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html
+ errors["NS_ERROR_DOM_INDEX_SIZE_ERR"] = FAILURE(1)
+ errors["NS_ERROR_DOM_HIERARCHY_REQUEST_ERR"] = FAILURE(3)
+ errors["NS_ERROR_DOM_WRONG_DOCUMENT_ERR"] = FAILURE(4)
+ errors["NS_ERROR_DOM_INVALID_CHARACTER_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(7)
+ errors["NS_ERROR_DOM_NOT_FOUND_ERR"] = FAILURE(8)
+ errors["NS_ERROR_DOM_NOT_SUPPORTED_ERR"] = FAILURE(9)
+ errors["NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR"] = FAILURE(10)
+ errors["NS_ERROR_DOM_INVALID_STATE_ERR"] = FAILURE(11)
+ errors["NS_ERROR_DOM_SYNTAX_ERR"] = FAILURE(12)
+ errors["NS_ERROR_DOM_INVALID_MODIFICATION_ERR"] = FAILURE(13)
+ errors["NS_ERROR_DOM_NAMESPACE_ERR"] = FAILURE(14)
+ errors["NS_ERROR_DOM_INVALID_ACCESS_ERR"] = FAILURE(15)
+ errors["NS_ERROR_DOM_TYPE_MISMATCH_ERR"] = FAILURE(17)
+ errors["NS_ERROR_DOM_SECURITY_ERR"] = FAILURE(18)
+ errors["NS_ERROR_DOM_NETWORK_ERR"] = FAILURE(19)
+ errors["NS_ERROR_DOM_ABORT_ERR"] = FAILURE(20)
+ errors["NS_ERROR_DOM_URL_MISMATCH_ERR"] = FAILURE(21)
+ errors["NS_ERROR_DOM_QUOTA_EXCEEDED_ERR"] = FAILURE(22)
+ errors["NS_ERROR_DOM_TIMEOUT_ERR"] = FAILURE(23)
+ errors["NS_ERROR_DOM_INVALID_NODE_TYPE_ERR"] = FAILURE(24)
+ errors["NS_ERROR_DOM_DATA_CLONE_ERR"] = FAILURE(25)
+ # StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding
+ errors["NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR"] = FAILURE(28)
+ # WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/
+ errors["NS_ERROR_DOM_UNKNOWN_ERR"] = FAILURE(30)
+ errors["NS_ERROR_DOM_DATA_ERR"] = FAILURE(31)
+ errors["NS_ERROR_DOM_OPERATION_ERR"] = FAILURE(32)
+ # https://heycam.github.io/webidl/#notallowederror
+ errors["NS_ERROR_DOM_NOT_ALLOWED_ERR"] = FAILURE(33)
+ # DOM error codes defined by us
+ errors["NS_ERROR_DOM_WRONG_TYPE_ERR"] = FAILURE(1002)
+ errors["NS_ERROR_DOM_NOT_NUMBER_ERR"] = FAILURE(1005)
+ errors["NS_ERROR_DOM_PROP_ACCESS_DENIED"] = FAILURE(1010)
+ errors["NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED"] = FAILURE(1011)
+ errors["NS_ERROR_DOM_BAD_URI"] = FAILURE(1012)
+ errors["NS_ERROR_DOM_RETVAL_UNDEFINED"] = FAILURE(1013)
+
+ # A way to represent uncatchable exceptions
+ errors["NS_ERROR_UNCATCHABLE_EXCEPTION"] = FAILURE(1015)
+
+ errors["NS_ERROR_DOM_MALFORMED_URI"] = FAILURE(1016)
+ errors["NS_ERROR_DOM_INVALID_HEADER_NAME"] = FAILURE(1017)
+
+ errors["NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT"] = FAILURE(1018)
+
+ # When manipulating the bytecode cache with the JS API, some transcoding
+ # errors, such as a different bytecode format can cause failures of the
+ # decoding process.
+ errors["NS_ERROR_DOM_JS_DECODING_ERROR"] = FAILURE(1026)
+
+ # Image decode errors.
+ errors["NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT"] = FAILURE(1027)
+ errors["NS_ERROR_DOM_IMAGE_INVALID_REQUEST"] = FAILURE(1028)
+ errors["NS_ERROR_DOM_IMAGE_BROKEN"] = FAILURE(1029)
+
+ # Used to indicate that a resource with the Cross-Origin-Resource-Policy
+ # response header set failed the origin check.
+ # https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header
+ errors["NS_ERROR_DOM_CORP_FAILED"] = FAILURE(1036)
+
+ # Used to indicate that a URI may not be loaded into a cross-origin
+ # context.
+ errors["NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI"] = FAILURE(1037)
+
+ # The request failed because there are too many recursive iframes or
+ # objects being loaded.
+ errors["NS_ERROR_RECURSIVE_DOCUMENT_LOAD"] = FAILURE(1038)
+
+ # WebExtension content script may not load this URL.
+ errors["NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI"] = FAILURE(1039)
+
+ # Used to indicate that a resource load was blocked because of the
+ # Cross-Origin-Embedder-Policy response header.
+ # https://html.spec.whatwg.org/multipage/origin.html#coep
+ errors["NS_ERROR_DOM_COEP_FAILED"] = FAILURE(1040)
+
+ # Used to indicate that a resource load was blocked because of the
+ # Cross-Origin-Opener-Policy response header.
+ # https://html.spec.whatwg.org/multipage/origin.html#cross-origin-opener-policies
+ errors["NS_ERROR_DOM_COOP_FAILED"] = FAILURE(1041)
+
+ errors["NS_ERROR_DOM_INVALID_HEADER_VALUE"] = FAILURE(1042)
+
+ # May be used to indicate when e.g. setting a property value didn't
+ # actually change the value, like for obj.foo = "bar"; obj.foo = "bar";
+ # the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.
+ errors["NS_SUCCESS_DOM_NO_OPERATION"] = SUCCESS(1)
+
+ # A success code that indicates that evaluating a string of JS went
+ # just fine except it threw an exception. Only for legacy use by
+ # nsJSUtils.
+ errors["NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW"] = SUCCESS(2)
+
+ # A success code that indicates that evaluating a string of JS went
+ # just fine except it was killed by an uncatchable exception.
+ # Only for legacy use by nsJSUtils.
+ errors["NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE"] = SUCCESS(3)
+
+
+# =======================================================================
+# 15: NS_ERROR_MODULE_IMGLIB
+# =======================================================================
+with modules["IMGLIB"]:
+ errors["NS_IMAGELIB_ERROR_FAILURE"] = FAILURE(5)
+ errors["NS_IMAGELIB_ERROR_NO_DECODER"] = FAILURE(6)
+ errors["NS_IMAGELIB_ERROR_NOT_FINISHED"] = FAILURE(7)
+ errors["NS_IMAGELIB_ERROR_NO_ENCODER"] = FAILURE(9)
+
+
+# =======================================================================
+# 17: NS_ERROR_MODULE_EDITOR
+# =======================================================================
+with modules["EDITOR"]:
+ errors["NS_ERROR_EDITOR_DESTROYED"] = FAILURE(1)
+
+ # An error code that indicates that the DOM tree has been modified by
+ # web app or add-on while the editor modifying the tree. However,
+ # this shouldn't be exposed to the web because the result should've
+ # been expected by the web app.
+ errors["NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE"] = FAILURE(2)
+
+ # An error code that indicates that the edit action canceled by
+ # clipboard event listener or beforeinput event listener. Note that
+ # don't make this as a success code since it's not check with NS_FAILED()
+ # and may keep handling the operation unexpectedly.
+ errors["NS_ERROR_EDITOR_ACTION_CANCELED"] = FAILURE(3)
+
+ # An error code that indicates that there is no editable selection ranges.
+ # E.g., Selection has no range, caret is in non-editable element,
+ # non-collapsed range crosses editing host boundaries.
+ errors["NS_ERROR_EDITOR_NO_EDITABLE_RANGE"] = FAILURE(4)
+
+ errors["NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND"] = SUCCESS(1)
+ errors["NS_SUCCESS_EDITOR_FOUND_TARGET"] = SUCCESS(2)
+
+ # If most callers ignore error except serious error (like
+ # NS_ERROR_EDITOR_DESTROYED), this success code is useful. E.g. such
+ # callers can do:
+ # nsresult rv = Foo();
+ # if (MOZ_UNLIKELY(NS_FAILED(rv))) {
+ # NS_WARNING("Foo() failed");
+ # return rv;
+ # }
+ # NS_WARNING_ASSERTION(
+ # rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
+ # "Foo() failed, but ignored");
+ errors["NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR"] = SUCCESS(3)
+
+
+# =======================================================================
+# 18: NS_ERROR_MODULE_XPCONNECT
+# =======================================================================
+with modules["XPCONNECT"]:
+ errors["NS_ERROR_XPC_NOT_ENOUGH_ARGS"] = FAILURE(1)
+ errors["NS_ERROR_XPC_NEED_OUT_OBJECT"] = FAILURE(2)
+ errors["NS_ERROR_XPC_CANT_SET_OUT_VAL"] = FAILURE(3)
+ errors["NS_ERROR_XPC_NATIVE_RETURNED_FAILURE"] = FAILURE(4)
+ errors["NS_ERROR_XPC_CANT_GET_INTERFACE_INFO"] = FAILURE(5)
+ errors["NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO"] = FAILURE(6)
+ errors["NS_ERROR_XPC_CANT_GET_METHOD_INFO"] = FAILURE(7)
+ errors["NS_ERROR_XPC_UNEXPECTED"] = FAILURE(8)
+ errors["NS_ERROR_XPC_BAD_CONVERT_JS"] = FAILURE(9)
+ errors["NS_ERROR_XPC_BAD_CONVERT_NATIVE"] = FAILURE(10)
+ errors["NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF"] = FAILURE(11)
+ errors["NS_ERROR_XPC_BAD_OP_ON_WN_PROTO"] = FAILURE(12)
+ errors["NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN"] = FAILURE(13)
+ errors["NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN"] = FAILURE(14)
+ errors["NS_ERROR_XPC_CANT_WATCH_WN_STATIC"] = FAILURE(15)
+ errors["NS_ERROR_XPC_CANT_EXPORT_WN_STATIC"] = FAILURE(16)
+ errors["NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED"] = FAILURE(17)
+ errors["NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED"] = FAILURE(18)
+ errors["NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE"] = FAILURE(19)
+ errors["NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE"] = FAILURE(20)
+ errors["NS_ERROR_XPC_CI_RETURNED_FAILURE"] = FAILURE(21)
+ errors["NS_ERROR_XPC_GS_RETURNED_FAILURE"] = FAILURE(22)
+ errors["NS_ERROR_XPC_BAD_CID"] = FAILURE(23)
+ errors["NS_ERROR_XPC_BAD_IID"] = FAILURE(24)
+ errors["NS_ERROR_XPC_CANT_CREATE_WN"] = FAILURE(25)
+ errors["NS_ERROR_XPC_JS_THREW_EXCEPTION"] = FAILURE(26)
+ errors["NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT"] = FAILURE(27)
+ errors["NS_ERROR_XPC_JS_THREW_JS_OBJECT"] = FAILURE(28)
+ errors["NS_ERROR_XPC_JS_THREW_NULL"] = FAILURE(29)
+ errors["NS_ERROR_XPC_JS_THREW_STRING"] = FAILURE(30)
+ errors["NS_ERROR_XPC_JS_THREW_NUMBER"] = FAILURE(31)
+ errors["NS_ERROR_XPC_JAVASCRIPT_ERROR"] = FAILURE(32)
+ errors["NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"] = FAILURE(33)
+ errors["NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY"] = FAILURE(34)
+ errors["NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY"] = FAILURE(35)
+ errors["NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY"] = FAILURE(36)
+ errors["NS_ERROR_XPC_CANT_GET_ARRAY_INFO"] = FAILURE(37)
+ errors["NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING"] = FAILURE(38)
+ errors["NS_ERROR_XPC_SECURITY_MANAGER_VETO"] = FAILURE(39)
+ errors["NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE"] = FAILURE(40)
+ errors["NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS"] = FAILURE(41)
+ errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT"] = FAILURE(43)
+ errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE"] = FAILURE(44)
+ errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD"] = FAILURE(45)
+ errors["NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE"] = FAILURE(46)
+ errors["NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED"] = FAILURE(47)
+ errors["NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED"] = FAILURE(48)
+ errors["NS_ERROR_XPC_BAD_ID_STRING"] = FAILURE(49)
+ errors["NS_ERROR_XPC_BAD_INITIALIZER_NAME"] = FAILURE(50)
+ errors["NS_ERROR_XPC_HAS_BEEN_SHUTDOWN"] = FAILURE(51)
+ errors["NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN"] = FAILURE(52)
+ errors["NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL"] = FAILURE(53)
+ # any new errors here should have an associated entry added in xpc.msg
+
+
+# =======================================================================
+# 19: NS_ERROR_MODULE_PROFILE
+# =======================================================================
+with modules["PROFILE"]:
+ errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200)
+ errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201)
+ errors["NS_ERROR_DATABASE_CHANGED"] = FAILURE(202)
+
+
+# =======================================================================
+# 21: NS_ERROR_MODULE_SECURITY
+# =======================================================================
+with modules["SECURITY"]:
+ # Error code for XFO
+ errors["NS_ERROR_XFO_VIOLATION"] = FAILURE(96)
+
+ # Error code for CSP
+ errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(97)
+ errors["NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION"] = FAILURE(98)
+
+ # Error code for Sub-Resource Integrity
+ errors["NS_ERROR_SRI_CORRUPT"] = FAILURE(200)
+ errors["NS_ERROR_SRI_NOT_ELIGIBLE"] = FAILURE(201)
+ errors["NS_ERROR_SRI_UNEXPECTED_HASH_TYPE"] = FAILURE(202)
+ errors["NS_ERROR_SRI_IMPORT"] = FAILURE(203)
+
+ # CMS specific nsresult error codes. Note: the numbers used here correspond
+ # to the values in nsICMSMessageErrors.idl.
+ errors["NS_ERROR_CMS_VERIFY_NOT_SIGNED"] = FAILURE(1024)
+ errors["NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO"] = FAILURE(1025)
+ errors["NS_ERROR_CMS_VERIFY_BAD_DIGEST"] = FAILURE(1026)
+ errors["NS_ERROR_CMS_VERIFY_NOCERT"] = FAILURE(1028)
+ errors["NS_ERROR_CMS_VERIFY_UNTRUSTED"] = FAILURE(1029)
+ errors["NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED"] = FAILURE(1031)
+ errors["NS_ERROR_CMS_VERIFY_ERROR_PROCESSING"] = FAILURE(1032)
+ errors["NS_ERROR_CMS_VERIFY_BAD_SIGNATURE"] = FAILURE(1033)
+ errors["NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH"] = FAILURE(1034)
+ errors["NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO"] = FAILURE(1035)
+ errors["NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO"] = FAILURE(1036)
+ errors["NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE"] = FAILURE(1037)
+ errors["NS_ERROR_CMS_VERIFY_HEADER_MISMATCH"] = FAILURE(1038)
+ errors["NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED"] = FAILURE(1039)
+ errors["NS_ERROR_CMS_VERIFY_CERT_WITHOUT_ADDRESS"] = FAILURE(1040)
+ errors["NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG"] = FAILURE(1056)
+ errors["NS_ERROR_CMS_ENCRYPT_INCOMPLETE"] = FAILURE(1057)
+
+
+# =======================================================================
+# 24: NS_ERROR_MODULE_URILOADER
+# =======================================================================
+with modules["URILOADER"]:
+ errors["NS_ERROR_WONT_HANDLE_CONTENT"] = FAILURE(1)
+ # The load has been cancelled because it was found on a malware or phishing
+ # list.
+ errors["NS_ERROR_MALWARE_URI"] = FAILURE(30)
+ errors["NS_ERROR_PHISHING_URI"] = FAILURE(31)
+ errors["NS_ERROR_TRACKING_URI"] = FAILURE(34)
+ errors["NS_ERROR_UNWANTED_URI"] = FAILURE(35)
+ errors["NS_ERROR_BLOCKED_URI"] = FAILURE(37)
+ errors["NS_ERROR_HARMFUL_URI"] = FAILURE(38)
+ errors["NS_ERROR_FINGERPRINTING_URI"] = FAILURE(41)
+ errors["NS_ERROR_CRYPTOMINING_URI"] = FAILURE(42)
+ errors["NS_ERROR_SOCIALTRACKING_URI"] = FAILURE(43)
+ errors["NS_ERROR_EMAILTRACKING_URI"] = FAILURE(44)
+ # Used when "Save Link As..." doesn't see the headers quickly enough to
+ # choose a filename. See nsContextMenu.js.
+ errors["NS_ERROR_SAVE_LINK_AS_TIMEOUT"] = FAILURE(32)
+ # Used when the data from a channel has already been parsed and cached so it
+ # doesn't need to be reparsed from the original source.
+ errors["NS_ERROR_PARSED_DATA_CACHED"] = FAILURE(33)
+
+ # When browser.tabs.documentchannel.parent-controlled pref and SHIP
+ # are enabled and a load gets cancelled due to another one
+ # starting, the error is NS_BINDING_CANCELLED_OLD_LOAD.
+ errors["NS_BINDING_CANCELLED_OLD_LOAD"] = FAILURE(39)
+
+
+# =======================================================================
+# 25: NS_ERROR_MODULE_CONTENT
+# =======================================================================
+with modules["CONTENT"]:
+ # Error codes for content policy blocking
+ errors["NS_ERROR_CONTENT_BLOCKED"] = FAILURE(6)
+ errors["NS_ERROR_CONTENT_BLOCKED_SHOW_ALT"] = FAILURE(7)
+ # Success variations of content policy blocking
+ errors["NS_PROPTABLE_PROP_NOT_THERE"] = FAILURE(10)
+ # Error code for when the content process crashed
+ errors["NS_ERROR_CONTENT_CRASHED"] = FAILURE(16)
+ # Error code for when a subframe process crashed
+ errors["NS_ERROR_FRAME_CRASHED"] = FAILURE(14)
+ # Error code for when the content process had a different buildID than the
+ # parent
+ errors["NS_ERROR_BUILDID_MISMATCH"] = FAILURE(17)
+
+ errors["NS_PROPTABLE_PROP_OVERWRITTEN"] = SUCCESS(11)
+ # Error codes for FindBroadcaster in XULBroadcastManager.cpp
+ errors["NS_FINDBROADCASTER_NOT_FOUND"] = SUCCESS(12)
+ errors["NS_FINDBROADCASTER_FOUND"] = SUCCESS(13)
+
+
+# =======================================================================
+# 27: NS_ERROR_MODULE_XSLT
+# =======================================================================
+with modules["XSLT"]:
+ errors["NS_ERROR_XPATH_INVALID_ARG"] = errors["NS_ERROR_INVALID_ARG"]
+
+ errors["NS_ERROR_XSLT_PARSE_FAILURE"] = FAILURE(1)
+ errors["NS_ERROR_XPATH_PARSE_FAILURE"] = FAILURE(2)
+ errors["NS_ERROR_XSLT_ALREADY_SET"] = FAILURE(3)
+ errors["NS_ERROR_XSLT_EXECUTION_FAILURE"] = FAILURE(4)
+ errors["NS_ERROR_XPATH_UNKNOWN_FUNCTION"] = FAILURE(5)
+ errors["NS_ERROR_XSLT_BAD_RECURSION"] = FAILURE(6)
+ errors["NS_ERROR_XSLT_BAD_VALUE"] = FAILURE(7)
+ errors["NS_ERROR_XSLT_NODESET_EXPECTED"] = FAILURE(8)
+ errors["NS_ERROR_XSLT_ABORTED"] = FAILURE(9)
+ errors["NS_ERROR_XSLT_NETWORK_ERROR"] = FAILURE(10)
+ errors["NS_ERROR_XSLT_WRONG_MIME_TYPE"] = FAILURE(11)
+ errors["NS_ERROR_XSLT_LOAD_RECURSION"] = FAILURE(12)
+ errors["NS_ERROR_XPATH_BAD_ARGUMENT_COUNT"] = FAILURE(13)
+ errors["NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION"] = FAILURE(14)
+ errors["NS_ERROR_XPATH_PAREN_EXPECTED"] = FAILURE(15)
+ errors["NS_ERROR_XPATH_INVALID_AXIS"] = FAILURE(16)
+ errors["NS_ERROR_XPATH_NO_NODE_TYPE_TEST"] = FAILURE(17)
+ errors["NS_ERROR_XPATH_BRACKET_EXPECTED"] = FAILURE(18)
+ errors["NS_ERROR_XPATH_INVALID_VAR_NAME"] = FAILURE(19)
+ errors["NS_ERROR_XPATH_UNEXPECTED_END"] = FAILURE(20)
+ errors["NS_ERROR_XPATH_OPERATOR_EXPECTED"] = FAILURE(21)
+ errors["NS_ERROR_XPATH_UNCLOSED_LITERAL"] = FAILURE(22)
+ errors["NS_ERROR_XPATH_BAD_COLON"] = FAILURE(23)
+ errors["NS_ERROR_XPATH_BAD_BANG"] = FAILURE(24)
+ errors["NS_ERROR_XPATH_ILLEGAL_CHAR"] = FAILURE(25)
+ errors["NS_ERROR_XPATH_BINARY_EXPECTED"] = FAILURE(26)
+ errors["NS_ERROR_XSLT_LOAD_BLOCKED_ERROR"] = FAILURE(27)
+ errors["NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED"] = FAILURE(28)
+ errors["NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE"] = FAILURE(29)
+ errors["NS_ERROR_XSLT_BAD_NODE_NAME"] = FAILURE(30)
+ errors["NS_ERROR_XSLT_VAR_ALREADY_SET"] = FAILURE(31)
+ errors["NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED"] = FAILURE(32)
+
+ errors["NS_XSLT_GET_NEW_HANDLER"] = SUCCESS(1)
+
+
+# =======================================================================
+# 28: NS_ERROR_MODULE_IPC
+# =======================================================================
+with modules["IPC"]:
+ # Initial creation of a Transport object failed internally for unknown reasons.
+ errors["NS_ERROR_TRANSPORT_INIT"] = FAILURE(1)
+ # Generic error related to duplicating handle failures.
+ errors["NS_ERROR_DUPLICATE_HANDLE"] = FAILURE(2)
+ # Bridging: failure trying to open the connection to the parent
+ errors["NS_ERROR_BRIDGE_OPEN_PARENT"] = FAILURE(3)
+ # Bridging: failure trying to open the connection to the child
+ errors["NS_ERROR_BRIDGE_OPEN_CHILD"] = FAILURE(4)
+
+
+# =======================================================================
+# 30: NS_ERROR_MODULE_STORAGE
+# =======================================================================
+with modules["STORAGE"]:
+ # To add additional errors to Storage, please append entries to the bottom
+ # of the list in the following format:
+ # NS_ERROR_STORAGE_YOUR_ERR, FAILURE(n)
+ # where n is the next unique positive integer. You must also add an entry
+ # to js/xpconnect/src/xpc.msg under the code block beginning with the
+ # comment 'storage related codes (from mozStorage.h)', in the following
+ # format: 'XPC_MSG_DEF(NS_ERROR_STORAGE_YOUR_ERR, "brief description of your
+ # error")'
+ errors["NS_ERROR_STORAGE_BUSY"] = FAILURE(1)
+ errors["NS_ERROR_STORAGE_IOERR"] = FAILURE(2)
+ errors["NS_ERROR_STORAGE_CONSTRAINT"] = FAILURE(3)
+
+
+# =======================================================================
+# 32: NS_ERROR_MODULE_DOM_FILE
+# =======================================================================
+with modules["DOM_FILE"]:
+ errors["NS_ERROR_DOM_FILE_NOT_FOUND_ERR"] = FAILURE(0)
+ errors["NS_ERROR_DOM_FILE_NOT_READABLE_ERR"] = FAILURE(1)
+
+
+# =======================================================================
+# 33: NS_ERROR_MODULE_DOM_INDEXEDDB
+# =======================================================================
+with modules["DOM_INDEXEDDB"]:
+ # IndexedDB error codes http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
+ errors["NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR"] = FAILURE(1)
+ errors["NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR"] = FAILURE(3)
+ errors["NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR"] = FAILURE(4)
+ errors["NS_ERROR_DOM_INDEXEDDB_DATA_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR"] = FAILURE(6)
+ errors["NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR"] = FAILURE(7)
+ errors["NS_ERROR_DOM_INDEXEDDB_ABORT_ERR"] = FAILURE(8)
+ errors["NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR"] = FAILURE(9)
+ errors["NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR"] = FAILURE(11)
+ errors["NS_ERROR_DOM_INDEXEDDB_VERSION_ERR"] = FAILURE(12)
+ errors["NS_ERROR_DOM_INDEXEDDB_KEY_ERR"] = FAILURE(1002)
+ errors["NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR"] = FAILURE(1003)
+ errors["NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR"] = FAILURE(1004)
+
+
+# =======================================================================
+# 34: NS_ERROR_MODULE_DOM_FILEHANDLE
+# =======================================================================
+with modules["DOM_FILEHANDLE"]:
+ errors["NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR"] = FAILURE(1)
+ errors["NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR"] = FAILURE(2)
+ errors["NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR"] = FAILURE(3)
+ errors["NS_ERROR_DOM_FILEHANDLE_ABORT_ERR"] = FAILURE(4)
+ errors["NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR"] = FAILURE(6)
+
+
+# =======================================================================
+# 35: NS_ERROR_MODULE_SIGNED_JAR
+# =======================================================================
+with modules["SIGNED_JAR"]:
+ errors["NS_ERROR_SIGNED_JAR_NOT_SIGNED"] = FAILURE(1)
+ errors["NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY"] = FAILURE(2)
+ errors["NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY"] = FAILURE(3)
+ errors["NS_ERROR_SIGNED_JAR_ENTRY_MISSING"] = FAILURE(4)
+ errors["NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE"] = FAILURE(5)
+ errors["NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE"] = FAILURE(6)
+ errors["NS_ERROR_SIGNED_JAR_ENTRY_INVALID"] = FAILURE(7)
+ errors["NS_ERROR_SIGNED_JAR_MANIFEST_INVALID"] = FAILURE(8)
+
+
+# =======================================================================
+# 36: NS_ERROR_MODULE_DOM_FILESYSTEM
+# =======================================================================
+with modules["DOM_FILESYSTEM"]:
+ errors["NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR"] = FAILURE(1)
+ errors["NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR"] = FAILURE(2)
+ errors["NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(3)
+ errors["NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR"] = FAILURE(4)
+ errors["NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR"] = FAILURE(6)
+
+
+# =======================================================================
+# 38: NS_ERROR_MODULE_SIGNED_APP
+# =======================================================================
+with modules["SIGNED_APP"]:
+ errors["NS_ERROR_SIGNED_APP_MANIFEST_INVALID"] = FAILURE(1)
+
+
+# =======================================================================
+# 40: NS_ERROR_MODULE_DOM_PUSH
+# =======================================================================
+with modules["DOM_PUSH"]:
+ errors["NS_ERROR_DOM_PUSH_DENIED_ERR"] = FAILURE(2)
+ errors["NS_ERROR_DOM_PUSH_ABORT_ERR"] = FAILURE(3)
+ errors["NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE"] = FAILURE(4)
+ errors["NS_ERROR_DOM_PUSH_INVALID_KEY_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR"] = FAILURE(6)
+
+
+# =======================================================================
+# 41: NS_ERROR_MODULE_DOM_MEDIA
+# =======================================================================
+with modules["DOM_MEDIA"]:
+ # HTMLMediaElement API errors from
+ # https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements
+ errors["NS_ERROR_DOM_MEDIA_ABORT_ERR"] = FAILURE(1)
+ errors["NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR"] = FAILURE(2)
+ errors["NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR"] = FAILURE(3)
+
+ # HTMLMediaElement internal decoding error
+ errors["NS_ERROR_DOM_MEDIA_DECODE_ERR"] = FAILURE(4)
+ errors["NS_ERROR_DOM_MEDIA_FATAL_ERR"] = FAILURE(5)
+ errors["NS_ERROR_DOM_MEDIA_METADATA_ERR"] = FAILURE(6)
+ errors["NS_ERROR_DOM_MEDIA_OVERFLOW_ERR"] = FAILURE(7)
+ errors["NS_ERROR_DOM_MEDIA_END_OF_STREAM"] = FAILURE(8)
+ errors["NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA"] = FAILURE(9)
+ errors["NS_ERROR_DOM_MEDIA_CANCELED"] = FAILURE(10)
+ errors["NS_ERROR_DOM_MEDIA_MEDIASINK_ERR"] = FAILURE(11)
+ errors["NS_ERROR_DOM_MEDIA_DEMUXER_ERR"] = FAILURE(12)
+ errors["NS_ERROR_DOM_MEDIA_CDM_ERR"] = FAILURE(13)
+ errors["NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER"] = FAILURE(14)
+ errors["NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER"] = FAILURE(15)
+ errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR"] = FAILURE(16)
+ errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR"] = FAILURE(17)
+ errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR"] = FAILURE(18)
+
+ # QuotaExceededError specializations
+ errors["NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR"] = FAILURE(30)
+ errors["NS_ERROR_DOM_MEDIA_SOURCE_MAX_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(31)
+ errors["NS_ERROR_DOM_MEDIA_SOURCE_FULL_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(32)
+
+ # Internal CDM error
+ errors["NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR"] = FAILURE(50)
+ errors["NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR"] = FAILURE(51)
+ errors["NS_ERROR_DOM_MEDIA_CDM_HDCP_NOT_SUPPORT"] = FAILURE(52)
+
+ # Internal platform-related errors
+ errors["NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR"] = FAILURE(101)
+ errors["NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR"] = FAILURE(102)
+ errors["NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR"] = FAILURE(103)
+ errors["NS_ERROR_DOM_MEDIA_DENIED_IN_NON_UTILITY"] = FAILURE(104)
+
+# =======================================================================
+# 42: NS_ERROR_MODULE_URL_CLASSIFIER
+# =======================================================================
+with modules["URL_CLASSIFIER"]:
+ # Errors during list updates
+ errors["NS_ERROR_UC_UPDATE_UNKNOWN"] = FAILURE(1)
+ errors["NS_ERROR_UC_UPDATE_DUPLICATE_PREFIX"] = FAILURE(2)
+ errors["NS_ERROR_UC_UPDATE_INFINITE_LOOP"] = FAILURE(3)
+ errors["NS_ERROR_UC_UPDATE_WRONG_REMOVAL_INDICES"] = FAILURE(4)
+ errors["NS_ERROR_UC_UPDATE_CHECKSUM_MISMATCH"] = FAILURE(5)
+ errors["NS_ERROR_UC_UPDATE_MISSING_CHECKSUM"] = FAILURE(6)
+ errors["NS_ERROR_UC_UPDATE_SHUTDOWNING"] = FAILURE(7)
+ errors["NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND"] = FAILURE(8)
+ errors["NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE"] = FAILURE(9)
+ errors["NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK"] = FAILURE(10)
+ errors["NS_ERROR_UC_UPDATE_UNEXPECTED_VERSION"] = FAILURE(11)
+
+ # Specific errors while parsing pver2/pver4 responses
+ errors["NS_ERROR_UC_PARSER_MISSING_PARAM"] = FAILURE(12)
+ errors["NS_ERROR_UC_PARSER_DECODE_FAILURE"] = FAILURE(13)
+ errors["NS_ERROR_UC_PARSER_UNKNOWN_THREAT"] = FAILURE(14)
+ errors["NS_ERROR_UC_PARSER_MISSING_VALUE"] = FAILURE(15)
+
+
+# =======================================================================
+# 43: NS_ERROR_MODULE_ERRORRESULT
+# =======================================================================
+with modules["ERRORRESULT"]:
+ # Represents a JS Value being thrown as an exception.
+ errors["NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION"] = FAILURE(1)
+ # Used to indicate that we want to throw a DOMException.
+ errors["NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION"] = FAILURE(2)
+ # Used to indicate that an exception is already pending on the JSContext.
+ errors["NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT"] = FAILURE(3)
+ # Used to indicate that we want to throw a TypeError.
+ errors["NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR"] = FAILURE(4)
+ # Used to indicate that we want to throw a RangeError.
+ errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5)
+
+
+# =======================================================================
+# 45: NS_ERROR_MODULE_WDBA
+# =======================================================================
+with modules["WDBA"]:
+ errors["NS_ERROR_WDBA_NO_PROGID"] = FAILURE(1)
+ errors["NS_ERROR_WDBA_HASH_CHECK"] = FAILURE(2)
+ errors["NS_ERROR_WDBA_REJECTED"] = FAILURE(3)
+ errors["NS_ERROR_WDBA_BUILD"] = FAILURE(4)
+
+
+# =======================================================================
+# 51: NS_ERROR_MODULE_GENERAL
+# =======================================================================
+with modules["GENERAL"]:
+ # Error code used internally by the incremental downloader to cancel the
+ # network channel when the download is already complete.
+ errors["NS_ERROR_DOWNLOAD_COMPLETE"] = FAILURE(1)
+ # Error code used internally by the incremental downloader to cancel the
+ # network channel when the response to a range request is 200 instead of
+ # 206.
+ errors["NS_ERROR_DOWNLOAD_NOT_PARTIAL"] = FAILURE(2)
+ errors["NS_ERROR_UNORM_MOREOUTPUT"] = FAILURE(33)
+
+ errors["NS_ERROR_DOCSHELL_REQUEST_REJECTED"] = FAILURE(1001)
+ # This is needed for displaying an error message when navigation is
+ # attempted on a document when printing The value arbitrary as long as it
+ # doesn't conflict with any of the other values in the errors in
+ # DisplayLoadError
+ errors["NS_ERROR_DOCUMENT_IS_PRINTMODE"] = FAILURE(2001)
+
+ errors["NS_SUCCESS_DONT_FIXUP"] = SUCCESS(1)
+ # This success code may be returned by nsIAppStartup::Run to indicate that
+ # the application should be restarted. This condition corresponds to the
+ # case in which nsIAppStartup::Quit was called with the eRestart flag.
+ errors["NS_SUCCESS_RESTART_APP"] = SUCCESS(1)
+
+ # a11y
+ # raised when current pivot's position is needed but it's not in the tree
+ errors["NS_ERROR_NOT_IN_TREE"] = FAILURE(38)
+
+ # see nsTextEquivUtils
+ errors["NS_OK_NO_NAME_CLAUSE_HANDLED"] = SUCCESS(34)
+
+ # Error code used to indicate that functionality has been blocked by the
+ # Policy Manager
+ errors["NS_ERROR_BLOCKED_BY_POLICY"] = FAILURE(3)
+
+
+# ============================================================================
+# Write out the resulting module declarations to C++ and rust files
+# ============================================================================
+
+
+def error_list_h(output):
+ output.write(
+ """
+/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */
+
+#ifndef ErrorList_h__
+#define ErrorList_h__
+
+#include <stdint.h>
+
+"""
+ )
+
+ output.write("#define NS_ERROR_MODULE_BASE_OFFSET {}\n".format(MODULE_BASE_OFFSET))
+
+ for mod, val in modules.items():
+ output.write("#define NS_ERROR_MODULE_{} {}\n".format(mod, val.num))
+
+ items = []
+ for error, val in errors.items():
+ items.append(" {} = 0x{:X}".format(error, val))
+ output.write(
+ """
+enum class nsresult : uint32_t
+{{
+{}
+}};
+
+""".format(
+ ",\n".join(items)
+ )
+ )
+
+ items = []
+ for error, val in errors.items():
+ items.append(" {0} = nsresult::{0}".format(error))
+
+ output.write(
+ """
+const nsresult
+{}
+;
+
+#endif // ErrorList_h__
+""".format(
+ ",\n".join(items)
+ )
+ )
+
+
+def error_names_internal_h(output):
+ """Generate ErrorNamesInternal.h, which is a header file declaring one
+ function, const char* GetErrorNameInternal(nsresult). This method is not
+ intended to be used by consumer code, which should instead call
+ GetErrorName in ErrorNames.h."""
+
+ output.write(
+ """
+/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */
+
+#ifndef ErrorNamesInternal_h__
+#define ErrorNamesInternal_h__
+
+#include "ErrorNames.h"
+
+namespace {
+
+const char*
+GetErrorNameInternal(nsresult rv)
+{
+ switch (rv) {
+"""
+ )
+
+ # NOTE: Making sure we don't write out duplicate values is important as
+ # we're using a switch statement to implement this.
+ seen = set()
+ for error, val in errors.items():
+ if val not in seen:
+ output.write(' case nsresult::{0}: return "{0}";\n'.format(error))
+ seen.add(val)
+
+ output.write(
+ """
+ default: return nullptr;
+ }
+} // GetErrorNameInternal
+
+} // namespace
+
+#endif // ErrorNamesInternal_h__
+"""
+ )
+
+
+def error_list_rs(output):
+ output.write(
+ """
+/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */
+
+use super::nsresult;
+
+"""
+ )
+
+ output.write(
+ "pub const NS_ERROR_MODULE_BASE_OFFSET: nsresult = nsresult({});\n".format(
+ MODULE_BASE_OFFSET
+ )
+ )
+
+ for mod, val in modules.items():
+ output.write(
+ "pub const NS_ERROR_MODULE_{}: nsresult = nsresult({});\n".format(
+ mod, val.num
+ )
+ )
+
+ for error, val in errors.items():
+ output.write("pub const {}: nsresult = nsresult(0x{:X});\n".format(error, val))
+
+
+def gen_jinja(output, input_filename):
+ # This is used to generate Java code for error lists, and can be expanded to
+ # other required contexts in the future if desired.
+ import os
+
+ from jinja2 import Environment, FileSystemLoader, StrictUndefined
+
+ # FileSystemLoader requires the path to the directory containing templates,
+ # not the file name of the template itself.
+ (path, leaf) = os.path.split(input_filename)
+ env = Environment(
+ loader=FileSystemLoader(path, encoding="utf-8"),
+ undefined=StrictUndefined,
+ )
+ tpl = env.get_template(leaf)
+
+ context = {
+ "MODULE_BASE_OFFSET": MODULE_BASE_OFFSET,
+ "modules": ((mod, val.num) for mod, val in modules.items()),
+ "errors": errors.items(),
+ }
+
+ tpl.stream(context).dump(output, encoding="utf-8")
diff --git a/xpcom/base/ErrorNames.cpp b/xpcom/base/ErrorNames.cpp
new file mode 100644
index 0000000000..42401aa860
--- /dev/null
+++ b/xpcom/base/ErrorNames.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ErrorNames.h"
+#include "nsString.h"
+#include "prerror.h"
+#include "MainThreadUtils.h"
+
+// Get the GetErrorNameInternal method
+#include "ErrorNamesInternal.h"
+
+namespace mozilla {
+
+const char* GetStaticErrorName(nsresult rv) { return GetErrorNameInternal(rv); }
+
+void GetErrorName(nsresult rv, nsACString& name) {
+ if (const char* errorName = GetErrorNameInternal(rv)) {
+ name.AssignASCII(errorName);
+ return;
+ }
+
+ bool isSecurityError = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY;
+
+ // NS_ERROR_MODULE_SECURITY is the only module that is "allowed" to
+ // synthesize nsresult error codes that are not listed in ErrorList.h. (The
+ // NS_ERROR_MODULE_SECURITY error codes are synthesized from NSPR error
+ // codes.)
+ MOZ_ASSERT(isSecurityError);
+
+ if (NS_SUCCEEDED(rv)) {
+ name.AssignLiteral("NS_ERROR_GENERATE_SUCCESS(");
+ } else {
+ name.AssignLiteral("NS_ERROR_GENERATE_FAILURE(");
+ }
+
+ if (isSecurityError) {
+ name.AppendLiteral("NS_ERROR_MODULE_SECURITY");
+ } else {
+ // This should never happen given the assertion above, so we don't bother
+ // trying to print a symbolic name for the module here.
+ name.AppendInt(NS_ERROR_GET_MODULE(rv));
+ }
+
+ name.AppendLiteral(", ");
+
+ const char* nsprName = nullptr;
+ if (isSecurityError && NS_IsMainThread()) {
+ // Invert the logic from NSSErrorsService::GetXPCOMFromNSSError
+ PRErrorCode nsprCode = -1 * static_cast<PRErrorCode>(NS_ERROR_GET_CODE(rv));
+ nsprName = PR_ErrorToName(nsprCode);
+
+ // All NSPR error codes defined by NSPR or NSS should have a name mapping.
+ MOZ_ASSERT(nsprName);
+ }
+
+ if (nsprName) {
+ name.AppendASCII(nsprName);
+ } else {
+ name.AppendInt(NS_ERROR_GET_CODE(rv));
+ }
+
+ name.AppendLiteral(")");
+}
+
+} // namespace mozilla
+
+extern "C" {
+
+// This is an extern "C" binding for the GetErrorName method which is used by
+// the nsresult rust bindings in xpcom/rust/nserror.
+void Gecko_GetErrorName(nsresult aRv, nsACString& aName) {
+ mozilla::GetErrorName(aRv, aName);
+}
+}
diff --git a/xpcom/base/ErrorNames.h b/xpcom/base/ErrorNames.h
new file mode 100644
index 0000000000..d209cc492b
--- /dev/null
+++ b/xpcom/base/ErrorNames.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ErrorNames_h
+#define mozilla_ErrorNames_h
+
+#include "nsError.h"
+#include "nsStringFwd.h"
+
+namespace mozilla {
+
+// Maps the given nsresult to its symbolic name. For example,
+// GetErrorName(NS_OK, name) will result in name == "NS_OK".
+// When the symbolic name is unknown, name will be of the form
+// "NS_ERROR_GENERATE_SUCCESS(<module>, <code>)" or
+// "NS_ERROR_GENERATE_FAILURE(<module>, <code>)".
+void GetErrorName(nsresult rv, nsACString& name);
+
+// Same as GetErrorName, except that only nsresult values with statically
+// known symbolic names are handled. For all other values, nullptr is
+// returned.
+const char* GetStaticErrorName(nsresult rv);
+
+} // namespace mozilla
+
+#endif // mozilla_ErrorNames_h
diff --git a/xpcom/base/GkRustUtils.cpp b/xpcom/base/GkRustUtils.cpp
new file mode 100644
index 0000000000..00d32e6f65
--- /dev/null
+++ b/xpcom/base/GkRustUtils.cpp
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gk_rust_utils_ffi_generated.h"
+#include "GkRustUtils.h"
+
+using namespace mozilla;
+
+/* static */
+bool GkRustUtils::ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor,
+ uint64_t& aOutMinor, uint64_t& aOutPatch) {
+ return GkRustUtils_ParseSemVer(&aVersion, &aOutMajor, &aOutMinor, &aOutPatch);
+}
diff --git a/xpcom/base/GkRustUtils.h b/xpcom/base/GkRustUtils.h
new file mode 100644
index 0000000000..b7f8c3b99c
--- /dev/null
+++ b/xpcom/base/GkRustUtils.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_GkRustUtils_h
+#define __mozilla_GkRustUtils_h
+
+#include "nsString.h"
+
+class GkRustUtils {
+ public:
+ static bool ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor,
+ uint64_t& aOutMinor, uint64_t& aOutPatch);
+};
+
+#endif
diff --git a/xpcom/base/HoldDropJSObjects.cpp b/xpcom/base/HoldDropJSObjects.cpp
new file mode 100644
index 0000000000..2b3a810d89
--- /dev/null
+++ b/xpcom/base/HoldDropJSObjects.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/HoldDropJSObjects.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+
+namespace mozilla {
+namespace cyclecollector {
+
+void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone) {
+ CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
+ rt->AddJSHolder(aHolder, aTracer, aZone);
+}
+
+void HoldJSObjectsImpl(nsISupports* aHolder) {
+ nsXPCOMCycleCollectionParticipant* participant = nullptr;
+ CallQueryInterface(aHolder, &participant);
+ MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!");
+ MOZ_ASSERT(
+ participant->CheckForRightISupports(aHolder),
+ "The result of QIing a JS holder should be the same as ToSupports");
+
+ HoldJSObjectsImpl(aHolder, participant);
+}
+
+void DropJSObjectsImpl(void* aHolder) {
+ CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
+ rt->RemoveJSHolder(aHolder);
+}
+
+void DropJSObjectsImpl(nsISupports* aHolder) {
+#ifdef DEBUG
+ nsXPCOMCycleCollectionParticipant* participant = nullptr;
+ CallQueryInterface(aHolder, &participant);
+ MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!");
+ MOZ_ASSERT(
+ participant->CheckForRightISupports(aHolder),
+ "The result of QIing a JS holder should be the same as ToSupports");
+#endif
+ DropJSObjectsImpl(static_cast<void*>(aHolder));
+}
+
+} // namespace cyclecollector
+
+} // namespace mozilla
diff --git a/xpcom/base/HoldDropJSObjects.h b/xpcom/base/HoldDropJSObjects.h
new file mode 100644
index 0000000000..e01590208f
--- /dev/null
+++ b/xpcom/base/HoldDropJSObjects.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_HoldDropJSObjects_h
+#define mozilla_HoldDropJSObjects_h
+
+#include <type_traits>
+#include "nsCycleCollectionNoteChild.h"
+
+class nsISupports;
+class nsScriptObjectTracer;
+class nsCycleCollectionParticipant;
+
+namespace JS {
+class Zone;
+}
+
+// Only HoldJSObjects and DropJSObjects should be called directly.
+
+namespace mozilla {
+namespace cyclecollector {
+
+void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer,
+ JS::Zone* aZone = nullptr);
+void HoldJSObjectsImpl(nsISupports* aHolder);
+void DropJSObjectsImpl(void* aHolder);
+void DropJSObjectsImpl(nsISupports* aHolder);
+
+} // namespace cyclecollector
+
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value,
+ typename P = typename T::NS_CYCLE_COLLECTION_INNERCLASS>
+struct HoldDropJSObjectsHelper {
+ static void Hold(T* aHolder) {
+ cyclecollector::HoldJSObjectsImpl(aHolder,
+ NS_CYCLE_COLLECTION_PARTICIPANT(T));
+ }
+ static void Drop(T* aHolder) { cyclecollector::DropJSObjectsImpl(aHolder); }
+};
+
+template <class T>
+struct HoldDropJSObjectsHelper<T, true> {
+ static void Hold(T* aHolder) {
+ cyclecollector::HoldJSObjectsImpl(ToSupports(aHolder));
+ }
+ static void Drop(T* aHolder) {
+ cyclecollector::DropJSObjectsImpl(ToSupports(aHolder));
+ }
+};
+
+/**
+ Classes that hold strong references to JS GC things such as `JSObjects` and
+ `JS::Values` (e.g. `JS::Heap<JSObject*> mFoo;`) must use these, generally by
+ calling `HoldJSObjects(this)` and `DropJSObjects(this)` in the ctor and dtor
+ respectively.
+
+ For classes that are wrapper cached and hold no other strong references to JS
+ GC things, there's no need to call these; it will be taken care of
+ automatically by nsWrapperCache.
+**/
+template <class T>
+void HoldJSObjects(T* aHolder) {
+ static_assert(!std::is_base_of<nsCycleCollectionParticipant, T>::value,
+ "Don't call this on the CC participant but on the object that "
+ "it's for (in an Unlink implementation it's usually stored in "
+ "a variable named 'tmp').");
+ HoldDropJSObjectsHelper<T>::Hold(aHolder);
+}
+
+template <class T>
+void DropJSObjects(T* aHolder) {
+ static_assert(!std::is_base_of<nsCycleCollectionParticipant, T>::value,
+ "Don't call this on the CC participant but on the object that "
+ "it's for (in an Unlink implementation it's usually stored in "
+ "a variable named 'tmp').");
+ HoldDropJSObjectsHelper<T>::Drop(aHolder);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_HoldDropJSObjects_h
diff --git a/xpcom/base/IntentionalCrash.h b/xpcom/base/IntentionalCrash.h
new file mode 100644
index 0000000000..3ee751f7c7
--- /dev/null
+++ b/xpcom/base/IntentionalCrash.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_IntentionalCrash_h
+#define mozilla_IntentionalCrash_h
+
+#include <string>
+#include <sstream>
+#include <stdlib.h>
+#include <stdio.h>
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h>
+#endif
+
+namespace mozilla {
+
+inline void NoteIntentionalCrash(const char* aProcessType, uint32_t aPid = 0) {
+// In opt builds we don't actually have the leak checking enabled, and the
+// sandbox doesn't allow writing to this path, so we just disable this
+// function's behaviour.
+#ifdef MOZ_DEBUG
+ char* f = getenv("XPCOM_MEM_BLOAT_LOG");
+ if (!f) {
+ return;
+ }
+
+ fprintf(stderr, "XPCOM_MEM_BLOAT_LOG: %s\n", f);
+
+ uint32_t processPid = aPid == 0 ? getpid() : aPid;
+
+ std::ostringstream bloatName;
+ std::string processType(aProcessType);
+ if (!processType.compare("default")) {
+ bloatName << f;
+ } else {
+ std::string bloatLog(f);
+
+ bool hasExt = false;
+ if (bloatLog.size() >= 4 &&
+ bloatLog.compare(bloatLog.size() - 4, 4, ".log", 4) == 0) {
+ hasExt = true;
+ bloatLog.erase(bloatLog.size() - 4, 4);
+ }
+
+ bloatName << bloatLog << "_" << processType << "_pid" << processPid;
+ if (hasExt) {
+ bloatName << ".log";
+ }
+ }
+
+ fprintf(stderr, "Writing to log: %s\n", bloatName.str().c_str());
+
+ FILE* processfd = fopen(bloatName.str().c_str(), "a");
+ if (processfd) {
+ fprintf(processfd, "\n==> process %d will purposefully crash\n",
+ processPid);
+ fclose(processfd);
+ }
+#endif
+}
+
+} // namespace mozilla
+
+#endif // mozilla_IntentionalCrash_h
diff --git a/xpcom/base/JSONStringWriteFuncs.h b/xpcom/base/JSONStringWriteFuncs.h
new file mode 100644
index 0000000000..65b688788c
--- /dev/null
+++ b/xpcom/base/JSONStringWriteFuncs.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef JSONSTRINGWRITEFUNCS_H
+#define JSONSTRINGWRITEFUNCS_H
+
+#include "mozilla/JSONWriter.h"
+#include "nsString.h"
+
+#include <type_traits>
+
+namespace mozilla {
+
+// JSONWriteFunc that writes to an owned string.
+template <typename StringType>
+class JSONStringWriteFunc final : public JSONWriteFunc {
+ static_assert(
+ !std::is_reference_v<StringType>,
+ "Use JSONStringRefWriteFunc instead to write to a referenced string");
+
+ public:
+ JSONStringWriteFunc() = default;
+
+ void Write(const Span<const char>& aStr) final { mString.Append(aStr); }
+
+ const StringType& StringCRef() const { return mString; }
+
+ StringType&& StringRRef() && { return std::move(mString); }
+
+ private:
+ StringType mString;
+};
+
+// JSONWriteFunc that writes to a given nsACString reference.
+class JSONStringRefWriteFunc final : public JSONWriteFunc {
+ public:
+ MOZ_IMPLICIT JSONStringRefWriteFunc(nsACString& aString) : mString(aString) {}
+
+ void Write(const Span<const char>& aStr) final { mString.Append(aStr); }
+
+ const nsACString& StringCRef() const { return mString; }
+
+ private:
+ nsACString& mString;
+};
+
+} // namespace mozilla
+
+#endif // JSONSTRINGWRITEFUNCS_H
diff --git a/xpcom/base/JSObjectHolder.cpp b/xpcom/base/JSObjectHolder.cpp
new file mode 100644
index 0000000000..5bcc3cabbf
--- /dev/null
+++ b/xpcom/base/JSObjectHolder.cpp
@@ -0,0 +1,9 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JSObjectHolder.h"
+
+NS_IMPL_ISUPPORTS(mozilla::JSObjectHolder, nsISupports)
diff --git a/xpcom/base/JSObjectHolder.h b/xpcom/base/JSObjectHolder.h
new file mode 100644
index 0000000000..d64d41e479
--- /dev/null
+++ b/xpcom/base/JSObjectHolder.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_JSObjectHolder_h
+#define mozilla_JSObjectHolder_h
+
+#include "js/RootingAPI.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+
+// This class is useful when something on one thread needs to keep alive
+// a JS Object from another thread. If they are both on the same thread, the
+// owning class should instead be made a cycle collected SCRIPT_HOLDER class.
+// This object should only be AddRefed and Released on the same thread as
+// mJSObject.
+//
+// Note that this keeps alive the JS object until it goes away, so be sure not
+// to create cycles that keep alive the holder.
+//
+// JSObjectHolder is ISupports to make it usable with
+// NS_ReleaseOnMainThread.
+class JSObjectHolder final : public nsISupports {
+ public:
+ JSObjectHolder(JSContext* aCx, JSObject* aObject) : mJSObject(aCx, aObject) {}
+
+ NS_DECL_ISUPPORTS
+
+ JSObject* GetJSObject() { return mJSObject; }
+
+ private:
+ ~JSObjectHolder() = default;
+
+ JS::PersistentRooted<JSObject*> mJSObject;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_JSObjectHolder_h
diff --git a/xpcom/base/LogCommandLineHandler.cpp b/xpcom/base/LogCommandLineHandler.cpp
new file mode 100644
index 0000000000..26e78670e9
--- /dev/null
+++ b/xpcom/base/LogCommandLineHandler.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LogCommandLineHandler.h"
+
+#include "mozilla/Tokenizer.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+void LoggingHandleCommandLineArgs(
+ int argc, char const* const* argv,
+ std::function<void(nsACString const&)> const& consumer) {
+ // Keeps the name of a pending env var (MOZ_LOG or MOZ_LOG_FILE) that
+ // we expect to get a value for in the next iterated arg.
+ // Used for the `-MOZ_LOG <modules>` form of argument.
+ nsAutoCString env;
+
+ auto const names = {"MOZ_LOG"_ns, "MOZ_LOG_FILE"_ns};
+
+ for (int arg = 1; arg < argc; ++arg) {
+ Tokenizer p(argv[arg]);
+
+ if (!env.IsEmpty() && p.CheckChar('-')) {
+ NS_WARNING(
+ "Expects value after -MOZ_LOG(_FILE) argument, but another argument "
+ "found");
+
+ // We only expect values for the pending env var, start over
+ p.Rollback();
+ env.Truncate();
+ }
+
+ if (env.IsEmpty()) {
+ if (!p.CheckChar('-')) {
+ continue;
+ }
+ // We accept `-MOZ_LOG` as well as `--MOZ_LOG`.
+ Unused << p.CheckChar('-');
+
+ for (auto const& name : names) {
+ if (!p.CheckWord(name)) {
+ continue;
+ }
+
+ env.Assign(name);
+ env.Append('=');
+ break;
+ }
+
+ if (env.IsEmpty()) {
+ // An unknonwn argument, ignore.
+ continue;
+ }
+
+ // We accept `-MOZ_LOG <modules>` as well as `-MOZ_LOG=<modules>`.
+
+ if (p.CheckEOF()) {
+ // We have a lone `-MOZ_LOG` arg, the next arg is expected to be
+ // the value, |env| is now prepared as `MOZ_LOG=`.
+ continue;
+ }
+
+ if (!p.CheckChar('=')) {
+ // There is a character after the arg name and it's not '=',
+ // ignore this argument.
+ NS_WARNING("-MOZ_LOG(_FILE) argument not in a proper form");
+
+ env.Truncate();
+ continue;
+ }
+ }
+
+ // This can be non-empty from previous iteration or in this iteration.
+ if (!env.IsEmpty()) {
+ nsDependentCSubstring value;
+ Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), value);
+ env.Append(value);
+
+ consumer(env);
+
+ env.Truncate();
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/LogCommandLineHandler.h b/xpcom/base/LogCommandLineHandler.h
new file mode 100644
index 0000000000..ef945d7980
--- /dev/null
+++ b/xpcom/base/LogCommandLineHandler.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LogCommandLineHandler_h
+#define LogCommandLineHandler_h
+
+#include <functional>
+#include "nsString.h"
+
+namespace mozilla {
+
+/**
+ * A helper function parsing provided command line arguments and handling two
+ * specific args:
+ *
+ * -MOZ_LOG=modulelist
+ * -MOZ_LOG_FILE=file/path
+ *
+ * both expecting an argument, and corresponding to the same-name environment
+ * variables we use for logging setup.
+ *
+ * When an argument is found in the proper form, the consumer callback is called
+ * with a string in a follwing form, note that we do this for every occurence,
+ * and not just at the end of the parsing:
+ *
+ * "MOZ_LOG=modulelist" or "MOZ_LOG_FILE=file/path"
+ *
+ * All the following forms of arguments of the application are possible:
+ *
+ * --MOZ_LOG modulelist
+ * -MOZ_LOG modulelist
+ * --MOZ_LOG=modulelist
+ * -MOZ_LOG=modulelist
+ *
+ * The motivation for a separte function and not implementing a command line
+ * handler interface is that we need to process this very early during the
+ * application startup. Command line handlers are proccessed way later
+ * after logging has already been set up.
+ */
+void LoggingHandleCommandLineArgs(
+ int argc, char const* const* argv,
+ std::function<void(nsACString const&)> const& consumer);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/LogModulePrefWatcher.cpp b/xpcom/base/LogModulePrefWatcher.cpp
new file mode 100644
index 0000000000..bd06660533
--- /dev/null
+++ b/xpcom/base/LogModulePrefWatcher.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LogModulePrefWatcher.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "base/process_util.h"
+
+static const char kLoggingPrefPrefix[] = "logging.";
+static const char kLoggingConfigPrefPrefix[] = "logging.config";
+static const int kLoggingConfigPrefixLen = sizeof(kLoggingConfigPrefPrefix) - 1;
+static const char kLoggingPrefClearOnStartup[] =
+ "logging.config.clear_on_startup";
+static const char kLoggingPrefLogFile[] = "logging.config.LOG_FILE";
+static const char kLoggingPrefAddTimestamp[] = "logging.config.add_timestamp";
+static const char kLoggingPrefSync[] = "logging.config.sync";
+static const char kLoggingPrefStacks[] = "logging.config.profilerstacks";
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(LogModulePrefWatcher, nsIObserver)
+
+/**
+ * Resets all the preferences in the logging. branch
+ * This is needed because we may crash while logging, and this would cause us
+ * to log after restarting as well.
+ *
+ * If logging after restart is desired, set the logging.config.clear_on_startup
+ * pref to false, or use the MOZ_LOG_FILE and MOZ_LOG_MODULES env vars.
+ */
+static void ResetExistingPrefs() {
+ nsTArray<nsCString> names;
+ nsresult rv =
+ Preferences::GetRootBranch()->GetChildList(kLoggingPrefPrefix, names);
+ if (NS_SUCCEEDED(rv)) {
+ for (auto& name : names) {
+ // Clearing the pref will cause it to reload, thus resetting the log level
+ Preferences::ClearUser(name.get());
+ }
+ }
+}
+
+/**
+ * Loads the log level from the given pref and updates the corresponding
+ * LogModule.
+ */
+static void LoadPrefValue(const char* aName) {
+ LogLevel logLevel = LogLevel::Disabled;
+
+ nsresult rv;
+ int32_t prefLevel = 0;
+ nsAutoCString prefValue;
+
+ if (strncmp(aName, kLoggingConfigPrefPrefix, kLoggingConfigPrefixLen) == 0) {
+ nsAutoCString prefName(aName);
+
+ if (prefName.EqualsLiteral(kLoggingPrefLogFile)) {
+ rv = Preferences::GetCString(aName, prefValue);
+ // The pref was reset. Clear the user file.
+ if (NS_FAILED(rv) || prefValue.IsEmpty()) {
+ LogModule::SetLogFile(nullptr);
+ return;
+ }
+
+ // If the pref value doesn't have a PID placeholder, append it to the end.
+ if (!strstr(prefValue.get(), MOZ_LOG_PID_TOKEN)) {
+ prefValue.AppendLiteral(MOZ_LOG_PID_TOKEN);
+ }
+
+ LogModule::SetLogFile(prefValue.BeginReading());
+ } else if (prefName.EqualsLiteral(kLoggingPrefAddTimestamp)) {
+ bool addTimestamp = Preferences::GetBool(aName, false);
+ LogModule::SetAddTimestamp(addTimestamp);
+ } else if (prefName.EqualsLiteral(kLoggingPrefSync)) {
+ bool sync = Preferences::GetBool(aName, false);
+ LogModule::SetIsSync(sync);
+ } else if (prefName.EqualsLiteral(kLoggingPrefStacks)) {
+ bool captureStacks = Preferences::GetBool(aName, false);
+ LogModule::SetCaptureStacks(captureStacks);
+ }
+ return;
+ }
+
+ if (Preferences::GetInt(aName, &prefLevel) == NS_OK) {
+ logLevel = ToLogLevel(prefLevel);
+ } else if (Preferences::GetCString(aName, prefValue) == NS_OK) {
+ if (prefValue.LowerCaseEqualsLiteral("error")) {
+ logLevel = LogLevel::Error;
+ } else if (prefValue.LowerCaseEqualsLiteral("warning")) {
+ logLevel = LogLevel::Warning;
+ } else if (prefValue.LowerCaseEqualsLiteral("info")) {
+ logLevel = LogLevel::Info;
+ } else if (prefValue.LowerCaseEqualsLiteral("debug")) {
+ logLevel = LogLevel::Debug;
+ } else if (prefValue.LowerCaseEqualsLiteral("verbose")) {
+ logLevel = LogLevel::Verbose;
+ }
+ }
+
+ const char* moduleName = aName + strlen(kLoggingPrefPrefix);
+ LogModule::Get(moduleName)->SetLevel(logLevel);
+}
+
+static void LoadExistingPrefs() {
+ nsIPrefBranch* root = Preferences::GetRootBranch();
+ if (!root) {
+ return;
+ }
+
+ nsTArray<nsCString> names;
+ nsresult rv = root->GetChildList(kLoggingPrefPrefix, names);
+ if (NS_SUCCEEDED(rv)) {
+ for (auto& name : names) {
+ LoadPrefValue(name.get());
+ }
+ }
+}
+
+LogModulePrefWatcher::LogModulePrefWatcher() = default;
+
+void LogModulePrefWatcher::RegisterPrefWatcher() {
+ RefPtr<LogModulePrefWatcher> prefWatcher = new LogModulePrefWatcher();
+ Preferences::AddStrongObserver(prefWatcher, kLoggingPrefPrefix);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService && XRE_IsParentProcess()) {
+ observerService->AddObserver(prefWatcher,
+ "browser-delayed-startup-finished", false);
+ }
+
+ LoadExistingPrefs();
+}
+
+NS_IMETHODIMP
+LogModulePrefWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic) == 0) {
+ NS_LossyConvertUTF16toASCII prefName(aData);
+ LoadPrefValue(prefName.get());
+ } else if (strcmp("browser-delayed-startup-finished", aTopic) == 0) {
+ bool clear = Preferences::GetBool(kLoggingPrefClearOnStartup, true);
+ if (clear) {
+ ResetExistingPrefs();
+ }
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "browser-delayed-startup-finished");
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/LogModulePrefWatcher.h b/xpcom/base/LogModulePrefWatcher.h
new file mode 100644
index 0000000000..92a2a3d5b7
--- /dev/null
+++ b/xpcom/base/LogModulePrefWatcher.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LogModulePrefWatcher_h
+#define LogModulePrefWatcher_h
+
+#include "nsIObserver.h"
+
+namespace mozilla {
+
+/**
+ * Watches for changes to "logging.*" prefs and then updates the appropriate
+ * LogModule's log level. Both the integer and string versions of the LogLevel
+ * enum are supported.
+ *
+ * For example setting the pref "logging.Foo" to "Verbose" will set the
+ * LogModule for "Foo" to the LogLevel::Verbose level. Setting "logging.Bar" to
+ * 4 would set the LogModule for "Bar" to the LogLevel::Debug level.
+ */
+class LogModulePrefWatcher : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ /**
+ * Starts observing logging pref changes.
+ */
+ static void RegisterPrefWatcher();
+
+ private:
+ LogModulePrefWatcher();
+ virtual ~LogModulePrefWatcher() = default;
+};
+} // namespace mozilla
+
+#endif // LogModulePrefWatcher_h
diff --git a/xpcom/base/Logging.cpp b/xpcom/base/Logging.cpp
new file mode 100644
index 0000000000..61899b77c7
--- /dev/null
+++ b/xpcom/base/Logging.cpp
@@ -0,0 +1,913 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include <algorithm>
+
+#include "base/process_util.h"
+#include "GeckoProfiler.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/LateWriteChecks.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Printf.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "MainThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "nsDebug.h"
+#include "nsDebugImpl.h"
+#include "nsPrintfCString.h"
+#include "NSPRLogModulesParser.h"
+#include "nsXULAppAPI.h"
+#include "LogCommandLineHandler.h"
+
+#include "prenv.h"
+#ifdef XP_WIN
+# include <fcntl.h>
+# include <process.h>
+#else
+# include <sys/stat.h> // for umask()
+# include <sys/types.h>
+# include <unistd.h>
+#endif
+
+// NB: Amount determined by performing a typical browsing session and finding
+// the maximum number of modules instantiated, and padding up to the next
+// power of 2.
+const uint32_t kInitialModuleCount = 256;
+// When rotate option is added to the modules list, this is the hardcoded
+// number of files we create and rotate. When there is rotate:40,
+// we will keep four files per process, each limited to 10MB. Sum is 40MB,
+// the given limit.
+//
+// (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add
+// another rule to allow logfile.?? be written by content processes.)
+const uint32_t kRotateFilesNumber = 4;
+
+namespace mozilla {
+
+namespace detail {
+
+void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt,
+ ...) {
+ va_list ap;
+ va_start(ap, aFmt);
+ aModule->Printv(aLevel, aFmt, ap);
+ va_end(ap);
+}
+
+void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart,
+ const char* aFmt, ...) {
+ va_list ap;
+ va_start(ap, aFmt);
+ aModule->Printv(aLevel, aStart, aFmt, ap);
+ va_end(ap);
+}
+
+} // namespace detail
+
+LogLevel ToLogLevel(int32_t aLevel) {
+ aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose));
+ aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled));
+ return static_cast<LogLevel>(aLevel);
+}
+
+static const char* ToLogStr(LogLevel aLevel) {
+ switch (aLevel) {
+ case LogLevel::Error:
+ return "E";
+ case LogLevel::Warning:
+ return "W";
+ case LogLevel::Info:
+ return "I";
+ case LogLevel::Debug:
+ return "D";
+ case LogLevel::Verbose:
+ return "V";
+ case LogLevel::Disabled:
+ default:
+ MOZ_CRASH("Invalid log level.");
+ return "";
+ }
+}
+
+namespace detail {
+
+/**
+ * A helper class providing reference counting for FILE*.
+ * It encapsulates the following:
+ * - the FILE handle
+ * - the order number it was created for when rotating (actual path)
+ * - number of active references
+ */
+class LogFile {
+ FILE* mFile;
+ uint32_t mFileNum;
+
+ public:
+ LogFile(FILE* aFile, uint32_t aFileNum)
+ : mFile(aFile), mFileNum(aFileNum), mNextToRelease(nullptr) {}
+
+ ~LogFile() {
+ fclose(mFile);
+ delete mNextToRelease;
+ }
+
+ FILE* File() const { return mFile; }
+ uint32_t Num() const { return mFileNum; }
+
+ LogFile* mNextToRelease;
+};
+
+static const char* ExpandLogFileName(const char* aFilename,
+ char (&buffer)[2048]) {
+ MOZ_ASSERT(aFilename);
+ static const char kPIDToken[] = MOZ_LOG_PID_TOKEN;
+ static const char kMOZLOGExt[] = MOZ_LOG_FILE_EXTENSION;
+
+ bool hasMozLogExtension = StringEndsWith(nsDependentCString(aFilename),
+ nsLiteralCString(kMOZLOGExt));
+
+ const char* pidTokenPtr = strstr(aFilename, kPIDToken);
+ if (pidTokenPtr &&
+ SprintfLiteral(buffer, "%.*s%s%" PRIPID "%s%s",
+ static_cast<int>(pidTokenPtr - aFilename), aFilename,
+ XRE_IsParentProcess() ? "-main." : "-child.",
+ base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken),
+ hasMozLogExtension ? "" : kMOZLOGExt) > 0) {
+ return buffer;
+ }
+
+ if (!hasMozLogExtension &&
+ SprintfLiteral(buffer, "%s%s", aFilename, kMOZLOGExt) > 0) {
+ return buffer;
+ }
+
+ return aFilename;
+}
+
+// Drop initial lines from the given file until it is less than or equal to the
+// given size.
+//
+// For simplicity and to reduce memory consumption, lines longer than the given
+// long line size may be broken.
+//
+// This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s`
+// and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in
+// Windows 7 so it's available.
+bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize,
+ uint16_t aLongLineSize = 16384) {
+ // `tempFilename` will be further updated below.
+ char tempFilename[2048];
+ SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename);
+
+ bool failedToWrite = false;
+
+ { // Scope `file` and `temp`, so that they are definitely closed.
+ ScopedCloseFile file(fopen(aFilename, "rb"));
+ if (!file) {
+ return false;
+ }
+
+ if (fseek(file.get(), 0, SEEK_END)) {
+ // If we can't seek for some reason, better to just not limit the log at
+ // all and hope to sort out large logs upon further analysis.
+ return false;
+ }
+
+ // `ftell` returns a positive `long`, which might be more than 32 bits.
+ uint64_t fileSize = static_cast<uint64_t>(ftell(file.get()));
+
+ if (fileSize <= aSize) {
+ return true;
+ }
+
+ uint64_t minBytesToDrop = fileSize - aSize;
+ uint64_t numBytesDropped = 0;
+
+ if (fseek(file.get(), 0, SEEK_SET)) {
+ // Same as above: if we can't seek, hope for the best.
+ return false;
+ }
+
+ ScopedCloseFile temp;
+
+#if defined(XP_WIN)
+ // This approach was cribbed from
+ // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158.
+ HANDLE hFile;
+ do {
+ // mkstemp isn't supported so keep trying until we get a file.
+ _mktemp_s(tempFilename, strlen(tempFilename) + 1);
+ hFile = CreateFileA(tempFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, nullptr);
+ } while (GetLastError() == ERROR_FILE_EXISTS);
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ NS_WARNING("INVALID_HANDLE_VALUE");
+ return false;
+ }
+
+ int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
+ if (fd == -1) {
+ NS_WARNING("_open_osfhandle failed!");
+ return false;
+ }
+
+ temp.reset(_fdopen(fd, "ab"));
+#elif defined(XP_UNIX)
+
+ // Coverity would prefer us to set a secure umask before using `mkstemp`.
+ // However, the umask is process-wide, so setting it may lead to difficult
+ // to debug complications; and it is fine for this particular short-lived
+ // temporary file to be insecure.
+ //
+ // coverity[SECURE_TEMP : FALSE]
+ int fd = mkstemp(tempFilename);
+ if (fd == -1) {
+ NS_WARNING("mkstemp failed!");
+ return false;
+ }
+ temp.reset(fdopen(fd, "ab"));
+#else
+# error Do not know how to open named temporary file
+#endif
+
+ if (!temp) {
+ NS_WARNING(nsPrintfCString("could not open named temporary file %s",
+ tempFilename)
+ .get());
+ return false;
+ }
+
+ // `fgets` always null terminates. If the line is too long, it won't
+ // include a trailing '\n' but will be null-terminated.
+ UniquePtr<char[]> line = MakeUnique<char[]>(aLongLineSize + 1);
+ while (fgets(line.get(), aLongLineSize + 1, file.get())) {
+ if (numBytesDropped >= minBytesToDrop) {
+ if (fputs(line.get(), temp.get()) < 0) {
+ NS_WARNING(
+ nsPrintfCString("fputs failed: ferror %d\n", ferror(temp.get()))
+ .get());
+ failedToWrite = true;
+ break;
+ }
+ } else {
+ // Binary mode avoids platform-specific wrinkles with text streams. In
+ // particular, on Windows, `\r\n` gets read as `\n` (and the reverse
+ // when writing), complicating this calculation.
+ numBytesDropped += strlen(line.get());
+ }
+ }
+ }
+
+ // At this point, `file` and `temp` are closed, so we can remove and rename.
+ if (failedToWrite) {
+ remove(tempFilename);
+ return false;
+ }
+
+#if defined(XP_WIN)
+ if (!::ReplaceFileA(aFilename, tempFilename, nullptr, 0, 0, 0)) {
+ NS_WARNING(
+ nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get());
+ return false;
+ }
+#elif defined(XP_UNIX)
+ if (rename(tempFilename, aFilename)) {
+ NS_WARNING(
+ nsPrintfCString("rename failed: %s (%d)\n", strerror(errno), errno)
+ .get());
+ return false;
+ }
+#else
+# error Do not know how to atomically replace file
+#endif
+
+ return true;
+}
+
+} // namespace detail
+
+namespace {
+// Helper method that initializes an empty va_list to be empty.
+void empty_va(va_list* va, ...) {
+ va_start(*va, va);
+ va_end(*va);
+}
+} // namespace
+
+class LogModuleManager {
+ public:
+ LogModuleManager()
+ : mModulesLock("logmodules"),
+ mModules(kInitialModuleCount),
+#ifdef DEBUG
+ mLoggingModuleRegistered(0),
+#endif
+ mPrintEntryCount(0),
+ mOutFile(nullptr),
+ mToReleaseFile(nullptr),
+ mOutFileNum(0),
+ mOutFilePath(strdup("")),
+ mMainThread(PR_GetCurrentThread()),
+ mSetFromEnv(false),
+ mAddTimestamp(false),
+ mCaptureProfilerStack(false),
+ mIsRaw(false),
+ mIsSync(false),
+ mRotate(0),
+ mInitialized(false) {
+ }
+
+ ~LogModuleManager() {
+ detail::LogFile* logFile = mOutFile.exchange(nullptr);
+ delete logFile;
+ }
+
+ /**
+ * Loads config from command line args or env vars if present, in
+ * this specific order of priority.
+ *
+ * Notes:
+ *
+ * 1) This function is only intended to be called once per session.
+ * 2) None of the functions used in Init should rely on logging.
+ */
+ void Init(int argc, char* argv[]) {
+ MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
+ mInitialized = true;
+
+ LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv),
+ [](nsACString const& env) {
+ // We deliberately set/rewrite the
+ // environment variables so that when child
+ // processes are spawned w/o passing the
+ // arguments they still inherit the logging
+ // settings as well as sandboxing can be
+ // correctly set. Scripts can pass
+ // -MOZ_LOG=$MOZ_LOG,modules as an argument
+ // to merge existing settings, if required.
+
+ // PR_SetEnv takes ownership of the string.
+ PR_SetEnv(ToNewCString(env));
+ });
+
+ bool shouldAppend = false;
+ bool addTimestamp = false;
+ bool isSync = false;
+ bool isRaw = false;
+ bool captureStacks = false;
+ int32_t rotate = 0;
+ int32_t maxSize = 0;
+ bool prependHeader = false;
+ const char* modules = PR_GetEnv("MOZ_LOG");
+ if (!modules || !modules[0]) {
+ modules = PR_GetEnv("MOZ_LOG_MODULES");
+ if (modules) {
+ NS_WARNING(
+ "MOZ_LOG_MODULES is deprecated."
+ "\nPlease use MOZ_LOG instead.");
+ }
+ }
+ if (!modules || !modules[0]) {
+ modules = PR_GetEnv("NSPR_LOG_MODULES");
+ if (modules) {
+ NS_WARNING(
+ "NSPR_LOG_MODULES is deprecated."
+ "\nPlease use MOZ_LOG instead.");
+ }
+ }
+
+ // Need to capture `this` since `sLogModuleManager` is not set until after
+ // initialization is complete.
+ NSPRLogModulesParser(
+ modules,
+ [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate, &maxSize,
+ &prependHeader, &captureStacks](const char* aName, LogLevel aLevel,
+ int32_t aValue) mutable {
+ if (strcmp(aName, "append") == 0) {
+ shouldAppend = true;
+ } else if (strcmp(aName, "timestamp") == 0) {
+ addTimestamp = true;
+ } else if (strcmp(aName, "sync") == 0) {
+ isSync = true;
+ } else if (strcmp(aName, "raw") == 0) {
+ isRaw = true;
+ } else if (strcmp(aName, "rotate") == 0) {
+ rotate = (aValue << 20) / kRotateFilesNumber;
+ } else if (strcmp(aName, "maxsize") == 0) {
+ maxSize = aValue << 20;
+ } else if (strcmp(aName, "prependheader") == 0) {
+ prependHeader = true;
+ } else if (strcmp(aName, "profilerstacks") == 0) {
+ captureStacks = true;
+ } else {
+ this->CreateOrGetModule(aName)->SetLevel(aLevel);
+ }
+ });
+
+ // Rotate implies timestamp to make the files readable
+ mAddTimestamp = addTimestamp || rotate > 0;
+ mIsSync = isSync;
+ mIsRaw = isRaw;
+ mRotate = rotate;
+ mCaptureProfilerStack = captureStacks;
+
+ if (rotate > 0 && shouldAppend) {
+ NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
+ }
+
+ if (rotate > 0 && maxSize > 0) {
+ NS_WARNING(
+ "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring "
+ "maxsize)");
+ maxSize = 0;
+ }
+
+ if (maxSize > 0 && !shouldAppend) {
+ NS_WARNING(
+ "MOZ_LOG: when you limit the log to maxsize, you must use append! "
+ "(ignorning maxsize)");
+ maxSize = 0;
+ }
+
+ if (rotate > 0 && prependHeader) {
+ NS_WARNING(
+ "MOZ_LOG: when you rotate the log, you cannot use prependheader!");
+ prependHeader = false;
+ }
+
+ const char* logFile = PR_GetEnv("MOZ_LOG_FILE");
+ if (!logFile || !logFile[0]) {
+ logFile = PR_GetEnv("NSPR_LOG_FILE");
+ }
+
+ if (logFile && logFile[0]) {
+ char buf[2048];
+ logFile = detail::ExpandLogFileName(logFile, buf);
+ mOutFilePath.reset(strdup(logFile));
+
+ if (mRotate > 0) {
+ // Delete all the previously captured files, including non-rotated
+ // log files, so that users don't complain our logs eat space even
+ // after the rotate option has been added and don't happen to send
+ // us old large logs along with the rotated files.
+ remove(mOutFilePath.get());
+ for (uint32_t i = 0; i < kRotateFilesNumber; ++i) {
+ RemoveFile(i);
+ }
+ }
+
+ mOutFile = OpenFile(shouldAppend, mOutFileNum, maxSize);
+ mSetFromEnv = true;
+ }
+
+ if (prependHeader && XRE_IsParentProcess()) {
+ va_list va;
+ empty_va(&va);
+ Print("Logger", LogLevel::Info, nullptr, "\n***\n\n", "Opening log\n",
+ va);
+ }
+ }
+
+ void SetLogFile(const char* aFilename) {
+ // For now we don't allow you to change the file at runtime.
+ if (mSetFromEnv) {
+ NS_WARNING(
+ "LogModuleManager::SetLogFile - Log file was set from the "
+ "MOZ_LOG_FILE environment variable.");
+ return;
+ }
+
+ const char* filename = aFilename ? aFilename : "";
+ char buf[2048];
+ filename = detail::ExpandLogFileName(filename, buf);
+
+ // Can't use rotate or maxsize at runtime yet.
+ MOZ_ASSERT(mRotate == 0,
+ "We don't allow rotate for runtime logfile changes");
+ mOutFilePath.reset(strdup(filename));
+
+ // Exchange mOutFile and set it to be released once all the writes are done.
+ detail::LogFile* newFile = OpenFile(false, 0);
+ detail::LogFile* oldFile = mOutFile.exchange(newFile);
+
+ // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set,
+ // and we don't allow log rotation when setting it at runtime,
+ // mToReleaseFile will be null, so we're not leaking.
+ DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile);
+ MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed");
+
+ // If we just need to release a file, we must force print, in order to
+ // trigger the closing and release of mToReleaseFile.
+ if (oldFile) {
+ va_list va;
+ empty_va(&va);
+ Print("Logger", LogLevel::Info, "Flushing old log files\n", va);
+ }
+ }
+
+ uint32_t GetLogFile(char* aBuffer, size_t aLength) {
+ uint32_t len = strlen(mOutFilePath.get());
+ if (len + 1 > aLength) {
+ return 0;
+ }
+ snprintf(aBuffer, aLength, "%s", mOutFilePath.get());
+ return len;
+ }
+
+ void SetIsSync(bool aIsSync) { mIsSync = aIsSync; }
+
+ void SetCaptureStacks(bool aCaptureStacks) {
+ mCaptureProfilerStack = aCaptureStacks;
+ }
+
+ void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; }
+
+ detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum,
+ uint32_t aMaxSize = 0) {
+ FILE* file;
+
+ if (mRotate > 0) {
+ char buf[2048];
+ SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
+
+ // rotate doesn't support append (or maxsize).
+ file = fopen(buf, "w");
+ } else if (aShouldAppend && aMaxSize > 0) {
+ detail::LimitFileToLessThanSize(mOutFilePath.get(), aMaxSize >> 1);
+ file = fopen(mOutFilePath.get(), "a");
+ } else {
+ file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w");
+ }
+
+ if (!file) {
+ return nullptr;
+ }
+
+ return new detail::LogFile(file, aFileNum);
+ }
+
+ void RemoveFile(uint32_t aFileNum) {
+ char buf[2048];
+ SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
+ remove(buf);
+ }
+
+ LogModule* CreateOrGetModule(const char* aName) {
+ OffTheBooksMutexAutoLock guard(mModulesLock);
+ return mModules
+ .LookupOrInsertWith(
+ aName,
+ [&] {
+#ifdef DEBUG
+ if (++mLoggingModuleRegistered > kInitialModuleCount) {
+ NS_WARNING(
+ "kInitialModuleCount too low, consider increasing its "
+ "value");
+ }
+#endif
+ return UniquePtr<LogModule>(
+ new LogModule{aName, LogLevel::Disabled});
+ })
+ .get();
+ }
+
+ void Print(const char* aName, LogLevel aLevel, const char* aFmt,
+ va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) {
+ Print(aName, aLevel, nullptr, "", aFmt, aArgs);
+ }
+
+ void Print(const char* aName, LogLevel aLevel, const TimeStamp* aStart,
+ const char* aPrepend, const char* aFmt, va_list aArgs)
+ MOZ_FORMAT_PRINTF(6, 0) {
+ AutoSuspendLateWriteChecks suspendLateWriteChecks;
+ long pid = static_cast<long>(base::GetCurrentProcId());
+ const size_t kBuffSize = 1024;
+ char buff[kBuffSize];
+
+ char* buffToWrite = buff;
+ SmprintfPointer allocatedBuff;
+
+ va_list argsCopy;
+ va_copy(argsCopy, aArgs);
+ int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy);
+ va_end(argsCopy);
+
+ if (charsWritten < 0) {
+ // Print out at least something. We must copy to the local buff,
+ // can't just assign aFmt to buffToWrite, since when
+ // buffToWrite != buff, we try to release it.
+ MOZ_ASSERT(false, "Probably incorrect format string in LOG?");
+ strncpy(buff, aFmt, kBuffSize - 1);
+ buff[kBuffSize - 1] = '\0';
+ charsWritten = strlen(buff);
+ } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) {
+ // We may have maxed out, allocate a buffer instead.
+ allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs);
+ buffToWrite = allocatedBuff.get();
+ charsWritten = strlen(buffToWrite);
+ }
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ struct LogMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("Log");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString8View& aModule,
+ const ProfilerString8View& aText) {
+ aWriter.StringProperty("module", aModule);
+ aWriter.StringProperty("name", aText);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.SetTableLabel("({marker.data.module}) {marker.data.name}");
+ schema.AddKeyLabelFormatSearchable("module", "Module",
+ MS::Format::String,
+ MS::Searchable::Searchable);
+ schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String,
+ MS::Searchable::Searchable);
+ return schema;
+ }
+ };
+
+ profiler_add_marker(
+ "LogMessages", geckoprofiler::category::OTHER,
+ {aStart ? MarkerTiming::IntervalUntilNowFrom(*aStart)
+ : MarkerTiming::InstantNow(),
+ MarkerStack::MaybeCapture(mCaptureProfilerStack)},
+ LogMarker{}, ProfilerString8View::WrapNullTerminatedString(aName),
+ ProfilerString8View::WrapNullTerminatedString(buffToWrite));
+ }
+
+ // Determine if a newline needs to be appended to the message.
+ const char* newline = "";
+ if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') {
+ newline = "\n";
+ }
+
+ FILE* out = stderr;
+
+ // In case we use rotate, this ensures the FILE is kept alive during
+ // its use. Increased before we load mOutFile.
+ ++mPrintEntryCount;
+
+ detail::LogFile* outFile = mOutFile;
+ if (outFile) {
+ out = outFile->File();
+ }
+
+ // This differs from the NSPR format in that we do not output the
+ // opaque system specific thread pointer (ie pthread_t) cast
+ // to a long. The address of the current PR_Thread continues to be
+ // prefixed.
+ //
+ // Additionally we prefix the output with the abbreviated log level
+ // and the module name.
+ PRThread* currentThread = PR_GetCurrentThread();
+ const char* currentThreadName = (mMainThread == currentThread)
+ ? "Main Thread"
+ : PR_GetThreadName(currentThread);
+
+ char noNameThread[40];
+ if (!currentThreadName) {
+ SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread);
+ currentThreadName = noNameThread;
+ }
+
+ if (!mAddTimestamp && !aStart) {
+ if (!mIsRaw) {
+ fprintf_stderr(out, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend,
+ nsDebugImpl::GetMultiprocessMode(), pid,
+ currentThreadName, ToLogStr(aLevel), aName, buffToWrite,
+ newline);
+ } else {
+ fprintf_stderr(out, "%s%s%s", aPrepend, buffToWrite, newline);
+ }
+ } else {
+ if (aStart) {
+ // XXX is there a reasonable way to convert one to the other? this is
+ // bad
+ PRTime prnow = PR_Now();
+ TimeStamp tmnow = TimeStamp::Now();
+ TimeDuration duration = tmnow - *aStart;
+ PRTime prstart = prnow - duration.ToMicroseconds();
+
+ PRExplodedTime now;
+ PRExplodedTime start;
+ PR_ExplodeTime(prnow, PR_GMTParameters, &now);
+ PR_ExplodeTime(prstart, PR_GMTParameters, &start);
+ // Ignore that the start time might be in a different day
+ fprintf_stderr(
+ out,
+ "%s%04d-%02d-%02d %02d:%02d:%02d.%06d -> %02d:%02d:%02d.%06d UTC "
+ "(%.1gms)- [%s %ld: %s]: %s/%s %s%s",
+ aPrepend, now.tm_year, now.tm_month + 1, start.tm_mday,
+ start.tm_hour, start.tm_min, start.tm_sec, start.tm_usec,
+ now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
+ duration.ToMilliseconds(), nsDebugImpl::GetMultiprocessMode(), pid,
+ currentThreadName, ToLogStr(aLevel), aName, buffToWrite, newline);
+ } else {
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
+ fprintf_stderr(out,
+ "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: "
+ "%s]: %s/%s %s%s",
+ aPrepend, now.tm_year, now.tm_month + 1, now.tm_mday,
+ now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
+ nsDebugImpl::GetMultiprocessMode(), pid,
+ currentThreadName, ToLogStr(aLevel), aName, buffToWrite,
+ newline);
+ }
+ }
+
+ if (mIsSync) {
+ fflush(out);
+ }
+
+ if (mRotate > 0 && outFile) {
+ int32_t fileSize = ftell(out);
+ if (fileSize > mRotate) {
+ uint32_t fileNum = outFile->Num();
+
+ uint32_t nextFileNum = fileNum + 1;
+ if (nextFileNum >= kRotateFilesNumber) {
+ nextFileNum = 0;
+ }
+
+ // And here is the trick. The current out-file remembers its order
+ // number. When no other thread shifted the global file number yet,
+ // we are the thread to open the next file.
+ if (mOutFileNum.compareExchange(fileNum, nextFileNum)) {
+ // We can work with mToReleaseFile because we are sure the
+ // mPrintEntryCount can't drop to zero now - the condition
+ // to actually delete what's stored in that member.
+ // And also, no other thread can enter this piece of code
+ // because mOutFile is still holding the current file with
+ // the non-shifted number. The compareExchange() above is
+ // a no-op for other threads.
+ outFile->mNextToRelease = mToReleaseFile;
+ mToReleaseFile = outFile;
+
+ mOutFile = OpenFile(false, nextFileNum);
+ }
+ }
+ }
+
+ if (--mPrintEntryCount == 0 && mToReleaseFile) {
+ // We were the last Print() entered, if there is a file to release
+ // do it now. exchange() is atomic and makes sure we release the file
+ // only once on one thread.
+ detail::LogFile* release = mToReleaseFile.exchange(nullptr);
+ delete release;
+ }
+ }
+
+ private:
+ OffTheBooksMutex mModulesLock;
+ nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;
+
+#ifdef DEBUG
+ Atomic<uint32_t, ReleaseAcquire> mLoggingModuleRegistered;
+#endif
+ // Print() entry counter, actually reflects concurrent use of the current
+ // output file. ReleaseAcquire ensures that manipulation with mOutFile
+ // and mToReleaseFile is synchronized by manipulation with this value.
+ Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount;
+ // File to write to. ReleaseAcquire because we need to sync mToReleaseFile
+ // with this.
+ Atomic<detail::LogFile*, ReleaseAcquire> mOutFile;
+ // File to be released when reference counter drops to zero. This member
+ // is assigned mOutFile when the current file has reached the limit.
+ // It can be Relaxed, since it's synchronized with mPrintEntryCount
+ // manipulation and we do atomic exchange() on it.
+ Atomic<detail::LogFile*, Relaxed> mToReleaseFile;
+ // The next file number. This is mostly only for synchronization sake.
+ // Can have relaxed ordering, since we only do compareExchange on it which
+ // is atomic regardless ordering.
+ Atomic<uint32_t, Relaxed> mOutFileNum;
+ // Just keeps the actual file path for further use.
+ UniqueFreePtr<char[]> mOutFilePath;
+
+ PRThread* mMainThread;
+ bool mSetFromEnv;
+ Atomic<bool, Relaxed> mAddTimestamp;
+ Atomic<bool, Relaxed> mCaptureProfilerStack;
+ Atomic<bool, Relaxed> mIsRaw;
+ Atomic<bool, Relaxed> mIsSync;
+ int32_t mRotate;
+ bool mInitialized;
+};
+
+StaticAutoPtr<LogModuleManager> sLogModuleManager;
+
+LogModule* LogModule::Get(const char* aName) {
+ // This is just a pass through to the LogModuleManager so
+ // that the LogModuleManager implementation can be kept internal.
+ MOZ_ASSERT(sLogModuleManager != nullptr);
+ return sLogModuleManager->CreateOrGetModule(aName);
+}
+
+void LogModule::SetLogFile(const char* aFilename) {
+ MOZ_ASSERT(sLogModuleManager);
+ sLogModuleManager->SetLogFile(aFilename);
+}
+
+uint32_t LogModule::GetLogFile(char* aBuffer, size_t aLength) {
+ MOZ_ASSERT(sLogModuleManager);
+ return sLogModuleManager->GetLogFile(aBuffer, aLength);
+}
+
+void LogModule::SetAddTimestamp(bool aAddTimestamp) {
+ sLogModuleManager->SetAddTimestamp(aAddTimestamp);
+}
+
+void LogModule::SetIsSync(bool aIsSync) {
+ sLogModuleManager->SetIsSync(aIsSync);
+}
+
+void LogModule::SetCaptureStacks(bool aCaptureStacks) {
+ sLogModuleManager->SetCaptureStacks(aCaptureStacks);
+}
+
+// This function is defined in gecko_logger/src/lib.rs
+// We mirror the level in rust code so we don't get forwarded all of the
+// rust logging and have to create an LogModule for each rust component.
+extern "C" void set_rust_log_level(const char* name, uint8_t level);
+
+void LogModule::SetLevel(LogLevel level) {
+ mLevel = level;
+
+ // If the log module contains `::` it is likely a rust module, so we
+ // pass the level into the rust code so it will know to forward the logging
+ // to Gecko.
+ if (strstr(mName, "::")) {
+ set_rust_log_level(mName, static_cast<uint8_t>(level));
+ }
+}
+
+void LogModule::Init(int argc, char* argv[]) {
+ // NB: This method is not threadsafe; it is expected to be called very early
+ // in startup prior to any other threads being run.
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (sLogModuleManager) {
+ // Already initialized.
+ return;
+ }
+
+ // NB: We intentionally do not register for ClearOnShutdown as that happens
+ // before all logging is complete. And, yes, that means we leak, but
+ // we're doing that intentionally.
+
+ // Don't assign the pointer until after Init is called. This should help us
+ // detect if any of the functions called by Init somehow rely on logging.
+ auto mgr = new LogModuleManager();
+ mgr->Init(argc, argv);
+ sLogModuleManager = mgr;
+}
+
+void LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const {
+ MOZ_ASSERT(sLogModuleManager != nullptr);
+
+ // Forward to LogModule manager w/ level and name
+ sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs);
+}
+
+void LogModule::Printv(LogLevel aLevel, const TimeStamp* aStart,
+ const char* aFmt, va_list aArgs) const {
+ MOZ_ASSERT(sLogModuleManager != nullptr);
+
+ // Forward to LogModule manager w/ level and name
+ sLogModuleManager->Print(Name(), aLevel, aStart, "", aFmt, aArgs);
+}
+
+} // namespace mozilla
+
+extern "C" {
+
+// This function is called by external code (rust) to log to one of our
+// log modules.
+void ExternMozLog(const char* aModule, mozilla::LogLevel aLevel,
+ const char* aMsg) {
+ MOZ_ASSERT(mozilla::sLogModuleManager != nullptr);
+
+ mozilla::LogModule* m =
+ mozilla::sLogModuleManager->CreateOrGetModule(aModule);
+ if (MOZ_LOG_TEST(m, aLevel)) {
+ mozilla::detail::log_print(m, aLevel, "%s", aMsg);
+ }
+}
+
+} // extern "C"
diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h
new file mode 100644
index 0000000000..6ed2752315
--- /dev/null
+++ b/xpcom/base/Logging.h
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_logging_h
+#define mozilla_logging_h
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+
+// We normally have logging enabled everywhere, but measurements showed that
+// having logging enabled on Android is quite expensive (hundreds of kilobytes
+// for both the format strings for logging and the code to perform all the
+// logging calls). Because retrieving logs from a mobile device is
+// comparatively more difficult for Android than it is for desktop and because
+// desktop machines tend to be less space/bandwidth-constrained than Android
+// devices, we've chosen to leave logging enabled on desktop, but disabled on
+// Android. Given that logging can still be useful for development purposes,
+// however, we leave logging enabled on Android developer builds.
+#if !defined(ANDROID) || !defined(RELEASE_OR_BETA)
+# define MOZ_LOGGING_ENABLED 1
+#else
+# define MOZ_LOGGING_ENABLED 0
+#endif
+
+// The mandatory extension we add to log files. Note that rotate will append
+// the file piece number still at the end.
+#define MOZ_LOG_FILE_EXTENSION ".moz_log"
+
+// Token for Process ID substitution.
+#define MOZ_LOG_PID_TOKEN "%PID"
+
+namespace mozilla {
+
+class TimeStamp;
+
+// While not a 100% mapping to PR_LOG's numeric values, mozilla::LogLevel does
+// maintain a direct mapping for the Disabled, Debug and Verbose levels.
+//
+// Mappings of LogLevel to PR_LOG's numeric values:
+//
+// +---------+------------------+-----------------+
+// | Numeric | NSPR Logging | Mozilla Logging |
+// +---------+------------------+-----------------+
+// | 0 | PR_LOG_NONE | Disabled |
+// | 1 | PR_LOG_ALWAYS | Error |
+// | 2 | PR_LOG_ERROR | Warning |
+// | 3 | PR_LOG_WARNING | Info |
+// | 4 | PR_LOG_DEBUG | Debug |
+// | 5 | PR_LOG_DEBUG + 1 | Verbose |
+// +---------+------------------+-----------------+
+//
+enum class LogLevel {
+ Disabled = 0,
+ Error,
+ Warning,
+ Info,
+ Debug,
+ Verbose,
+};
+
+/**
+ * Safely converts an integer into a valid LogLevel.
+ */
+LogLevel ToLogLevel(int32_t aLevel);
+
+class LogModule {
+ public:
+ ~LogModule() { ::free(mName); }
+
+ /**
+ * Retrieves the module with the given name. If it does not already exist
+ * it will be created.
+ *
+ * @param aName The name of the module.
+ * @return A log module for the given name. This may be shared.
+ */
+ static LogModule* Get(const char* aName);
+
+ /**
+ * Logging processes -MOZ_LOG and -MOZ_LOG_FILE command line arguments
+ * to override or set modules and the file as if passed through MOZ_LOG
+ * and MOZ_LOG_FILE env vars. It's fine to pass (0, nullptr) if args
+ * are not accessible in the caller's context, it will just do nothing.
+ * Note that the args take effect (are processed) only when this function
+ * is called the first time.
+ */
+ static void Init(int argc, char* argv[]);
+
+ /**
+ * Sets the log file to the given filename.
+ */
+ static void SetLogFile(const char* aFilename);
+
+ /**
+ * @param aBuffer - pointer to a buffer
+ * @param aLength - the length of the buffer
+ *
+ * @return the actual length of the filepath.
+ */
+ static uint32_t GetLogFile(char* aBuffer, size_t aLength);
+
+ /**
+ * @param aAddTimestamp If we should log a time stamp with every message.
+ */
+ static void SetAddTimestamp(bool aAddTimestamp);
+
+ /**
+ * @param aIsSync If we should flush the file after every logged message.
+ */
+ static void SetIsSync(bool aIsSync);
+
+ /**
+ * @param aCaptureStacks If we should capture stacks for the Firefox
+ * Profiler markers that are recorded for for each log entry.
+ */
+ static void SetCaptureStacks(bool aCaptureStacks);
+
+ /**
+ * Indicates whether or not the given log level is enabled.
+ */
+ bool ShouldLog(LogLevel aLevel) const { return mLevel >= aLevel; }
+
+ /**
+ * Retrieves the log module's current level.
+ */
+ LogLevel Level() const { return mLevel; }
+
+ /**
+ * Sets the log module's level.
+ */
+ void SetLevel(LogLevel level);
+
+ /**
+ * Print a log message for this module.
+ */
+ void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const
+ MOZ_FORMAT_PRINTF(3, 0);
+
+ void Printv(LogLevel aLevel, const TimeStamp* aStart, const char* aFmt,
+ va_list aArgs) const MOZ_FORMAT_PRINTF(4, 0);
+
+ /**
+ * Retrieves the module name.
+ */
+ const char* Name() const { return mName; }
+
+ private:
+ friend class LogModuleManager;
+
+ explicit LogModule(const char* aName, LogLevel aLevel)
+ : mName(strdup(aName)), mLevel(aLevel) {}
+
+ LogModule(LogModule&) = delete;
+ LogModule& operator=(const LogModule&) = delete;
+
+ char* mName;
+
+ Atomic<LogLevel, Relaxed> mLevel;
+};
+
+/**
+ * Helper class that lazy loads the given log module. This is safe to use for
+ * declaring static references to log modules and can be used as a replacement
+ * for accessing a LogModule directly.
+ *
+ * Example usage:
+ * static LazyLogModule sLayoutLog("layout");
+ *
+ * void Foo() {
+ * MOZ_LOG(sLayoutLog, LogLevel::Verbose, ("Entering foo"));
+ * }
+ */
+class LazyLogModule final {
+ public:
+ explicit constexpr LazyLogModule(const char* aLogName)
+ : mLogName(aLogName), mLog(nullptr) {}
+
+ MOZ_NEVER_INLINE_DEBUG operator LogModule*() {
+ // NB: The use of an atomic makes the reading and assignment of mLog
+ // thread-safe. There is a small chance that mLog will be set more
+ // than once, but that's okay as it will be set to the same LogModule
+ // instance each time. Also note LogModule::Get is thread-safe.
+ LogModule* tmp = mLog;
+ if (MOZ_UNLIKELY(!tmp)) {
+ tmp = LogModule::Get(mLogName);
+ mLog = tmp;
+ }
+
+ return tmp;
+ }
+
+ private:
+ const char* const mLogName;
+
+ Atomic<LogModule*, ReleaseAcquire> mLog;
+};
+
+namespace detail {
+
+inline bool log_test(const LogModule* module, LogLevel level) {
+ MOZ_ASSERT(level != LogLevel::Disabled);
+ return module && module->ShouldLog(level);
+}
+
+void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt, ...)
+ MOZ_FORMAT_PRINTF(3, 4);
+
+void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart,
+ const char* aFmt, ...) MOZ_FORMAT_PRINTF(4, 5);
+} // namespace detail
+
+} // namespace mozilla
+
+// Helper macro used convert MOZ_LOG's third parameter, |_args|, from a
+// parenthesized form to a varargs form. For example:
+// ("%s", "a message") => "%s", "a message"
+#define MOZ_LOG_EXPAND_ARGS(...) __VA_ARGS__
+
+#if MOZ_LOGGING_ENABLED
+# define MOZ_LOG_TEST(_module, _level) \
+ MOZ_UNLIKELY(mozilla::detail::log_test(_module, _level))
+#else
+// Define away MOZ_LOG_TEST here so the compiler will fold away entire
+// logging blocks via dead code elimination, e.g.:
+//
+// if (MOZ_LOG_TEST(...)) {
+// ...compute things to log and log them...
+// }
+# define MOZ_LOG_TEST(_module, _level) false
+#endif
+
+// The natural definition of the MOZ_LOG macro would expand to:
+//
+// do {
+// if (MOZ_LOG_TEST(_module, _level)) {
+// mozilla::detail::log_print(_module, ...);
+// }
+// } while (0)
+//
+// However, since _module is a LazyLogModule, and we need to call
+// LazyLogModule::operator() to get a LogModule* for the MOZ_LOG_TEST
+// macro and for the logging call, we'll wind up doing *two* calls, one
+// for each, rather than a single call. The compiler is not able to
+// fold the two calls into one, and the extra call can have a
+// significant effect on code size. (Making LazyLogModule::operator() a
+// `const` function does not have any effect.)
+//
+// Therefore, we will have to make a single call ourselves. But again,
+// the natural definition:
+//
+// do {
+// ::mozilla::LogModule* real_module = _module;
+// if (MOZ_LOG_TEST(real_module, _level)) {
+// mozilla::detail::log_print(real_module, ...);
+// }
+// } while (0)
+//
+// also has a problem: if logging is disabled, then we will call
+// LazyLogModule::operator() unnecessarily, and the compiler will not be
+// able to optimize away the call as dead code. We would like to avoid
+// such a scenario, as the whole point of disabling logging is for the
+// logging statements to not generate any code.
+//
+// Therefore, we need different definitions of MOZ_LOG, depending on
+// whether logging is enabled or not. (We need an actual definition of
+// MOZ_LOG even when logging is disabled to ensure the compiler sees that
+// variables only used during logging code are actually used, even if the
+// code will never be executed.) Hence, the following code.
+//
+// MOZ_LOG_DURATION takes a start time, and will generate a time range
+// in the logs. Also, if the Firefox Profiler is running,
+// MOZ_LOG_DURATION will generate a marker with a time duration
+// instead of a single point in time.
+#if MOZ_LOGGING_ENABLED
+# define MOZ_LOG(_module, _level, _args) \
+ do { \
+ const ::mozilla::LogModule* moz_real_module = _module; \
+ if (MOZ_LOG_TEST(moz_real_module, _level)) { \
+ mozilla::detail::log_print(moz_real_module, _level, \
+ MOZ_LOG_EXPAND_ARGS _args); \
+ } \
+ } while (0)
+# define MOZ_LOG_DURATION(_module, _level, start, _args) \
+ do { \
+ const ::mozilla::LogModule* moz_real_module = _module; \
+ if (MOZ_LOG_TEST(moz_real_module, _level)) { \
+ mozilla::detail::log_print(moz_real_module, _level, start, \
+ MOZ_LOG_EXPAND_ARGS _args); \
+ } \
+ } while (0)
+#else
+# define MOZ_LOG(_module, _level, _args) \
+ do { \
+ if (MOZ_LOG_TEST(_module, _level)) { \
+ mozilla::detail::log_print(_module, _level, \
+ MOZ_LOG_EXPAND_ARGS _args); \
+ } \
+ } while (0)
+# define MOZ_LOG_DURATION(_module, _level, start, _args) \
+ do { \
+ if (MOZ_LOG_TEST(_module, _level)) { \
+ mozilla::detail::log_print(_module, _level, start, \
+ MOZ_LOG_EXPAND_ARGS _args); \
+ } \
+ } while (0)
+#endif
+
+// This #define is a Logging.h-only knob! Don't encourage people to get fancy
+// with their log definitions by exporting it outside of Logging.h.
+#undef MOZ_LOGGING_ENABLED
+
+#endif // mozilla_logging_h
diff --git a/xpcom/base/MacHelpers.h b/xpcom/base/MacHelpers.h
new file mode 100644
index 0000000000..baf4321034
--- /dev/null
+++ b/xpcom/base/MacHelpers.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MacHelpers_h
+#define mozilla_MacHelpers_h
+
+#include "nsString.h"
+
+namespace mozilla {
+
+nsresult GetSelectedCityInfo(nsAString& aCountryCode);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/MacHelpers.mm b/xpcom/base/MacHelpers.mm
new file mode 100644
index 0000000000..26c04c49dd
--- /dev/null
+++ b/xpcom/base/MacHelpers.mm
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "MacHelpers.h"
+#include "MacStringHelpers.h"
+#include "nsObjCExceptions.h"
+
+#import <Foundation/Foundation.h>
+
+namespace mozilla {
+
+nsresult GetSelectedCityInfo(nsAString& aCountryCode) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // Can be replaced with [[NSLocale currentLocale] countryCode] once we build
+ // with the 10.12 SDK.
+ id countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
+
+ if (![countryCode isKindOfClass:[NSString class]]) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mozilla::CopyCocoaStringToXPCOMString((NSString*)countryCode,
+ aCountryCode);
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/MacStringHelpers.h b/xpcom/base/MacStringHelpers.h
new file mode 100644
index 0000000000..c2f9ee82dc
--- /dev/null
+++ b/xpcom/base/MacStringHelpers.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MacStringHelpers_h
+#define mozilla_MacStringHelpers_h
+
+#include "nsString.h"
+
+#import <Foundation/Foundation.h>
+
+namespace mozilla {
+
+nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/MacStringHelpers.mm b/xpcom/base/MacStringHelpers.mm
new file mode 100644
index 0000000000..cf0b03665d
--- /dev/null
+++ b/xpcom/base/MacStringHelpers.mm
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MacStringHelpers.h"
+#include "nsObjCExceptions.h"
+
+#include "mozilla/IntegerTypeTraits.h"
+#include <limits>
+
+namespace mozilla {
+
+nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NSUInteger len = [aFrom length];
+ if (len > std::numeric_limits<nsAString::size_type>::max()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!aTo.SetLength(len, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ [aFrom getCharacters:reinterpret_cast<unichar*>(aTo.BeginWriting())
+ range:NSMakeRange(0, len)];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/MemoryInfo.cpp b/xpcom/base/MemoryInfo.cpp
new file mode 100644
index 0000000000..6ce7f2b768
--- /dev/null
+++ b/xpcom/base/MemoryInfo.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MemoryInfo.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include <algorithm>
+#include <windows.h>
+
+namespace mozilla {
+
+/* static */
+MemoryInfo MemoryInfo::Get(const void* aPtr, size_t aSize) {
+ MemoryInfo result;
+
+ result.mStart = uintptr_t(aPtr);
+ const char* ptr = reinterpret_cast<const char*>(aPtr);
+ const char* end = ptr + aSize;
+ DebugOnly<void*> base = nullptr;
+ while (ptr < end) {
+ MEMORY_BASIC_INFORMATION basicInfo;
+ if (!VirtualQuery(ptr, &basicInfo, sizeof(basicInfo))) {
+ break;
+ }
+
+ MOZ_ASSERT_IF(base, base == basicInfo.AllocationBase);
+ base = basicInfo.AllocationBase;
+
+ size_t regionSize =
+ std::min(size_t(basicInfo.RegionSize), size_t(end - ptr));
+
+ if (basicInfo.State == MEM_COMMIT) {
+ result.mCommitted += regionSize;
+ } else if (basicInfo.State == MEM_RESERVE) {
+ result.mReserved += regionSize;
+ } else if (basicInfo.State == MEM_FREE) {
+ result.mFree += regionSize;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected region state");
+ }
+ result.mSize += regionSize;
+ ptr += regionSize;
+
+ if (result.mType.isEmpty()) {
+ if (basicInfo.Type & MEM_IMAGE) {
+ result.mType += PageType::Image;
+ }
+ if (basicInfo.Type & MEM_MAPPED) {
+ result.mType += PageType::Mapped;
+ }
+ if (basicInfo.Type & MEM_PRIVATE) {
+ result.mType += PageType::Private;
+ }
+
+ // The first 8 bits of AllocationProtect are an enum. The remaining bits
+ // are flags.
+ switch (basicInfo.AllocationProtect & 0xff) {
+ case PAGE_EXECUTE_WRITECOPY:
+ result.mPerms += Perm::CopyOnWrite;
+ [[fallthrough]];
+ case PAGE_EXECUTE_READWRITE:
+ result.mPerms += Perm::Write;
+ [[fallthrough]];
+ case PAGE_EXECUTE_READ:
+ result.mPerms += Perm::Read;
+ [[fallthrough]];
+ case PAGE_EXECUTE:
+ result.mPerms += Perm::Execute;
+ break;
+
+ case PAGE_WRITECOPY:
+ result.mPerms += Perm::CopyOnWrite;
+ [[fallthrough]];
+ case PAGE_READWRITE:
+ result.mPerms += Perm::Write;
+ [[fallthrough]];
+ case PAGE_READONLY:
+ result.mPerms += Perm::Read;
+ break;
+
+ default:
+ break;
+ }
+
+ if (basicInfo.AllocationProtect & PAGE_GUARD) {
+ result.mPerms += Perm::Guard;
+ }
+ if (basicInfo.AllocationProtect & PAGE_NOCACHE) {
+ result.mPerms += Perm::NoCache;
+ }
+ if (basicInfo.AllocationProtect & PAGE_WRITECOMBINE) {
+ result.mPerms += Perm::WriteCombine;
+ }
+ }
+ }
+
+ result.mEnd = uintptr_t(ptr);
+ return result;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/MemoryInfo.h b/xpcom/base/MemoryInfo.h
new file mode 100644
index 0000000000..d122f6d377
--- /dev/null
+++ b/xpcom/base/MemoryInfo.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MemoryInfo_h
+#define mozilla_MemoryInfo_h
+
+#include <cstddef>
+#include <cstdint>
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumSet.h"
+/**
+ * MemoryInfo is a helper class which describes the attributes and sizes of a
+ * particular region of VM memory on Windows. It roughtly corresponds to the
+ * values in a MEMORY_BASIC_INFORMATION struct, summed over an entire region or
+ * memory.
+ */
+
+namespace mozilla {
+
+class MemoryInfo final {
+ public:
+ enum class Perm : uint8_t {
+ Read,
+ Write,
+ Execute,
+ CopyOnWrite,
+ Guard,
+ NoCache,
+ WriteCombine,
+ };
+ enum class PageType : uint8_t {
+ Image,
+ Mapped,
+ Private,
+ };
+
+ using PermSet = EnumSet<Perm>;
+ using PageTypeSet = EnumSet<PageType>;
+
+ MemoryInfo() = default;
+ MOZ_IMPLICIT MemoryInfo(const MemoryInfo&) = default;
+
+ uintptr_t Start() const { return mStart; }
+ uintptr_t End() const { return mEnd; }
+
+ PageTypeSet Type() const { return mType; }
+ PermSet Perms() const { return mPerms; }
+
+ size_t Reserved() const { return mReserved; }
+ size_t Committed() const { return mCommitted; }
+ size_t Free() const { return mFree; }
+ size_t Size() const { return mSize; }
+
+ // Returns a MemoryInfo object containing the sums of all region sizes,
+ // divided into Reserved, Committed, and Free, depending on their State
+ // properties.
+ //
+ // The entire range of aSize bytes starting at aPtr must correspond to a
+ // single allocation. This restriction is enforced in debug builds.
+ static MemoryInfo Get(const void* aPtr, size_t aSize);
+
+ private:
+ uintptr_t mStart = 0;
+ uintptr_t mEnd = 0;
+
+ size_t mReserved = 0;
+ size_t mCommitted = 0;
+ size_t mFree = 0;
+ size_t mSize = 0;
+
+ PageTypeSet mType{};
+
+ PermSet mPerms{};
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MemoryInfo_h
diff --git a/xpcom/base/MemoryMapping.cpp b/xpcom/base/MemoryMapping.cpp
new file mode 100644
index 0000000000..3a91d7ceae
--- /dev/null
+++ b/xpcom/base/MemoryMapping.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MemoryMapping.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/FileUtils.h"
+
+#include <fstream>
+#include <string>
+#include <sstream>
+
+namespace mozilla {
+
+namespace {
+struct VMFlagString {
+ const char* mName;
+ const char* mPrettyName;
+ VMFlag mFlag;
+};
+
+static const VMFlagString sVMFlagStrings[] = {
+ // clang-format off
+ {"ac", "Accountable", VMFlag::Accountable},
+ {"ar", "ArchSpecific", VMFlag::ArchSpecific},
+ {"dc", "NoFork", VMFlag::NoFork},
+ {"dd", "NoCore", VMFlag::NoCore},
+ {"de", "NoExpand", VMFlag::NoExpand},
+ {"dw", "DisabledWrite", VMFlag::DisabledWrite},
+ {"ex", "Executable", VMFlag::Executable},
+ {"gd", "GrowsDown", VMFlag::GrowsDown},
+ {"hg", "HugePage", VMFlag::HugePage},
+ {"ht", "HugeTLB", VMFlag::HugeTLB},
+ {"io", "IO", VMFlag::IO},
+ {"lo", "Locked", VMFlag::Locked},
+ {"me", "MayExecute", VMFlag::MayExecute},
+ {"mg", "Mergeable", VMFlag::Mergeable},
+ {"mm", "MixedMap", VMFlag::MixedMap},
+ {"mr", "MayRead", VMFlag::MayRead},
+ {"ms", "MayShare", VMFlag::MayShare},
+ {"mw", "MayWrite", VMFlag::MayWrite},
+ {"nh", "NoHugePage", VMFlag::NoHugePage},
+ {"nl", "NonLinear", VMFlag::NonLinear},
+ {"nr", "NotReserved", VMFlag::NotReserved},
+ {"pf", "PurePFN", VMFlag::PurePFN},
+ {"rd", "Readable", VMFlag::Readable},
+ {"rr", "Random", VMFlag::Random},
+ {"sd", "SoftDirty", VMFlag::SoftDirty},
+ {"sh", "Shared", VMFlag::Shared},
+ {"sr", "Sequential", VMFlag::Sequential},
+ {"wr", "Writable", VMFlag::Writable},
+ // clang-format on
+};
+} // anonymous namespace
+
+constexpr size_t kVMFlags = size_t(-1);
+
+// An array of known field names which may be present in an smaps file, and the
+// offsets of the corresponding fields in a MemoryMapping class.
+const MemoryMapping::Field MemoryMapping::sFields[] = {
+ // clang-format off
+ {"AnonHugePages", offsetof(MemoryMapping, mAnonHugePages)},
+ {"Anonymous", offsetof(MemoryMapping, mAnonymous)},
+ {"KernelPageSize", offsetof(MemoryMapping, mKernelPageSize)},
+ {"LazyFree", offsetof(MemoryMapping, mLazyFree)},
+ {"Locked", offsetof(MemoryMapping, mLocked)},
+ {"MMUPageSize", offsetof(MemoryMapping, mMMUPageSize)},
+ {"Private_Clean", offsetof(MemoryMapping, mPrivate_Clean)},
+ {"Private_Dirty", offsetof(MemoryMapping, mPrivate_Dirty)},
+ {"Private_Hugetlb", offsetof(MemoryMapping, mPrivate_Hugetlb)},
+ {"Pss", offsetof(MemoryMapping, mPss)},
+ {"Referenced", offsetof(MemoryMapping, mReferenced)},
+ {"Rss", offsetof(MemoryMapping, mRss)},
+ {"Shared_Clean", offsetof(MemoryMapping, mShared_Clean)},
+ {"Shared_Dirty", offsetof(MemoryMapping, mShared_Dirty)},
+ {"Shared_Hugetlb", offsetof(MemoryMapping, mShared_Hugetlb)},
+ {"ShmemPmdMapped", offsetof(MemoryMapping, mShmemPmdMapped)},
+ {"Size", offsetof(MemoryMapping, mSize)},
+ {"Swap", offsetof(MemoryMapping, mSwap)},
+ {"SwapPss", offsetof(MemoryMapping, mSwapPss)},
+ // VmFlags is a special case. It contains an array of flag strings, which
+ // describe attributes of the mapping, rather than a mapping size. We include
+ // it in this array to aid in parsing, but give it a separate sentinel value,
+ // and treat it specially.
+ {"VmFlags", kVMFlags},
+ // clang-format on
+};
+
+template <typename T, int n>
+const T* FindEntry(const char* aName, const T (&aEntries)[n]) {
+ size_t index;
+ if (BinarySearchIf(
+ aEntries, 0, n,
+ [&](const T& aEntry) { return strcmp(aName, aEntry.mName); },
+ &index)) {
+ return &aEntries[index];
+ }
+ return nullptr;
+}
+
+using Perm = MemoryMapping::Perm;
+using PermSet = MemoryMapping::PermSet;
+
+nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, pid_t aPid) {
+ std::ifstream stream;
+ if (aPid == 0) {
+ stream.open("/proc/self/smaps");
+ } else {
+ std::ostringstream path;
+ path << "/proc/" << aPid << "/smaps" << std::ends;
+ stream.open(path.str());
+ }
+ if (stream.fail()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MemoryMapping* current = nullptr;
+ std::string line;
+ while (std::getline(stream, line)) {
+ size_t start, end, offset;
+ char flags[4] = "---";
+ char name[512];
+
+ name[0] = 0;
+
+ // clang-format off
+ // Match the start of an entry. A typical line looks something like:
+ //
+ // 1487118a7000-148711a5a000 r-xp 00000000 103:03 54004561 /usr/lib/libc-2.27.so
+ // clang-format on
+ if (sscanf(line.c_str(), "%zx-%zx %4c %zx %*u:%*u %*u %511s\n", &start,
+ &end, flags, &offset, name) >= 4) {
+ PermSet perms;
+ if (flags[0] == 'r') {
+ perms += Perm::Read;
+ }
+ if (flags[1] == 'w') {
+ perms += Perm::Write;
+ }
+ if (flags[2] == 'x') {
+ perms += Perm::Execute;
+ }
+ if (flags[3] == 'p') {
+ perms += Perm::Private;
+ } else if (flags[3] == 's') {
+ perms += Perm::Shared;
+ }
+
+ current = aMappings.AppendElement(
+ MemoryMapping{start, end, perms, offset, name});
+ continue;
+ }
+ if (!current) {
+ continue;
+ }
+
+ char* savePtr;
+ char* fieldName = strtok_r(line.data(), ":", &savePtr);
+ if (!fieldName) {
+ continue;
+ }
+ auto* field = FindEntry(fieldName, MemoryMapping::sFields);
+ if (!field) {
+ continue;
+ }
+
+ if (field->mOffset == kVMFlags) {
+ while (char* flagName = strtok_r(nullptr, " \n", &savePtr)) {
+ if (auto* flag = FindEntry(flagName, sVMFlagStrings)) {
+ current->mFlags += flag->mFlag;
+ }
+ }
+ continue;
+ }
+
+ const char* rest = strtok_r(nullptr, "\n", &savePtr);
+ size_t value;
+ if (sscanf(rest, "%zd kB", &value) > 0) {
+ current->ValueForField(*field) = value * 1024;
+ }
+ }
+
+ return NS_OK;
+}
+
+void MemoryMapping::Dump(nsACString& aOut) const {
+ aOut.AppendPrintf("%zx-%zx Size: %zu Offset: %zx %s\n", mStart, mEnd,
+ mEnd - mStart, mOffset, mName.get());
+
+ for (auto& field : MemoryMapping::sFields) {
+ if (field.mOffset < sizeof(*this)) {
+ aOut.AppendPrintf(" %s: %zd\n", field.mName, ValueForField(field));
+ }
+ }
+
+ aOut.AppendPrintf(" Flags: %x\n", mFlags.serialize());
+ for (auto& flag : sVMFlagStrings) {
+ if (mFlags.contains(flag.mFlag)) {
+ aOut.AppendPrintf(" : %s %s\n", flag.mName, flag.mPrettyName);
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/MemoryMapping.h b/xpcom/base/MemoryMapping.h
new file mode 100644
index 0000000000..51bbb0ab66
--- /dev/null
+++ b/xpcom/base/MemoryMapping.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MemoryMapping_h
+#define mozilla_MemoryMapping_h
+
+#include <cstdint>
+#include "mozilla/EnumSet.h"
+#include "nsString.h"
+#include "nsTArrayForwardDeclare.h"
+
+/**
+ * MemoryMapping is a helper class which describes an entry in the Linux
+ * /proc/<pid>/smaps file. See procfs(5) for details on the entry format.
+ *
+ * The GetMemoryMappings() function returns an array of such entries, sorted by
+ * start address, one for each entry in the current process's address space.
+ */
+
+namespace mozilla {
+
+enum class VMFlag : uint8_t {
+ Readable, // rd - readable
+ Writable, // wr - writable
+ Executable, // ex - executable
+ Shared, // sh - shared
+ MayRead, // mr - may read
+ MayWrite, // mw - may write
+ MayExecute, // me - may execute
+ MayShare, // ms - may share
+ GrowsDown, // gd - stack segment grows down
+ PurePFN, // pf - pure PFN range
+ DisabledWrite, // dw - disabled write to the mapped file
+ Locked, // lo - pages are locked in memory
+ IO, // io - memory mapped I/O area
+ Sequential, // sr - sequential read advise provided
+ Random, // rr - random read advise provided
+ NoFork, // dc - do not copy area on fork
+ NoExpand, // de - do not expand area on remapping
+ Accountable, // ac - area is accountable
+ NotReserved, // nr - swap space is not reserved for the area
+ HugeTLB, // ht - area uses huge tlb pages
+ NonLinear, // nl - non-linear mapping
+ ArchSpecific, // ar - architecture specific flag
+ NoCore, // dd - do not include area into core dump
+ SoftDirty, // sd - soft-dirty flag
+ MixedMap, // mm - mixed map area
+ HugePage, // hg - huge page advise flag
+ NoHugePage, // nh - no-huge page advise flag
+ Mergeable, // mg - mergeable advise flag
+};
+
+using VMFlagSet = EnumSet<VMFlag, uint32_t>;
+
+class MemoryMapping final {
+ public:
+ enum class Perm : uint8_t {
+ Read,
+ Write,
+ Execute,
+ Shared,
+ Private,
+ };
+
+ using PermSet = EnumSet<Perm>;
+
+ MemoryMapping(uintptr_t aStart, uintptr_t aEnd, PermSet aPerms,
+ size_t aOffset, const char* aName)
+ : mStart(aStart),
+ mEnd(aEnd),
+ mOffset(aOffset),
+ mName(aName),
+ mPerms(aPerms) {}
+
+ const nsCString& Name() const { return mName; }
+
+ uintptr_t Start() const { return mStart; }
+ uintptr_t End() const { return mEnd; }
+
+ bool Includes(const void* aPtr) const {
+ auto ptr = uintptr_t(aPtr);
+ return ptr >= mStart && ptr < mEnd;
+ }
+
+ PermSet Perms() const { return mPerms; }
+ VMFlagSet VMFlags() const { return mFlags; }
+
+ // For file mappings, the offset in the mapped file which corresponds to the
+ // start of the mapped region.
+ size_t Offset() const { return mOffset; }
+
+ size_t AnonHugePages() const { return mAnonHugePages; }
+ size_t Anonymous() const { return mAnonymous; }
+ size_t KernelPageSize() const { return mKernelPageSize; }
+ size_t LazyFree() const { return mLazyFree; }
+ size_t Locked() const { return mLocked; }
+ size_t MMUPageSize() const { return mMMUPageSize; }
+ size_t Private_Clean() const { return mPrivate_Clean; }
+ size_t Private_Dirty() const { return mPrivate_Dirty; }
+ size_t Private_Hugetlb() const { return mPrivate_Hugetlb; }
+ size_t Pss() const { return mPss; }
+ size_t Referenced() const { return mReferenced; }
+ size_t Rss() const { return mRss; }
+ size_t Shared_Clean() const { return mShared_Clean; }
+ size_t Shared_Dirty() const { return mShared_Dirty; }
+ size_t Shared_Hugetlb() const { return mShared_Hugetlb; }
+ size_t ShmemPmdMapped() const { return mShmemPmdMapped; }
+ size_t Size() const { return mSize; }
+ size_t Swap() const { return mSwap; }
+ size_t SwapPss() const { return mSwapPss; }
+
+ // Dumps a string representation of the entry, similar to its format in the
+ // smaps file, to the given string. Mainly useful for debugging.
+ void Dump(nsACString& aOut) const;
+
+ // These comparison operators are used for binary searching sorted arrays of
+ // MemoryMapping entries to find the one which contains a given pointer.
+ bool operator==(const void* aPtr) const { return Includes(aPtr); }
+ bool operator<(const void* aPtr) const { return mStart < uintptr_t(aPtr); }
+
+ private:
+ friend nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings,
+ pid_t aPid);
+
+ uintptr_t mStart = 0;
+ uintptr_t mEnd = 0;
+
+ size_t mOffset = 0;
+
+ nsCString mName;
+
+ // Members for size fields in the smaps file. Please keep these in sync with
+ // the sFields array.
+ size_t mAnonHugePages = 0;
+ size_t mAnonymous = 0;
+ size_t mKernelPageSize = 0;
+ size_t mLazyFree = 0;
+ size_t mLocked = 0;
+ size_t mMMUPageSize = 0;
+ size_t mPrivate_Clean = 0;
+ size_t mPrivate_Dirty = 0;
+ size_t mPrivate_Hugetlb = 0;
+ size_t mPss = 0;
+ size_t mReferenced = 0;
+ size_t mRss = 0;
+ size_t mShared_Clean = 0;
+ size_t mShared_Dirty = 0;
+ size_t mShared_Hugetlb = 0;
+ size_t mShmemPmdMapped = 0;
+ size_t mSize = 0;
+ size_t mSwap = 0;
+ size_t mSwapPss = 0;
+
+ PermSet mPerms{};
+ VMFlagSet mFlags{};
+
+ // Contains the name and offset of one of the above size_t fields, for use in
+ // parsing in dumping. The below helpers contain a list of the fields, and map
+ // Field entries to the appropriate member in a class instance.
+ struct Field {
+ const char* mName;
+ size_t mOffset;
+ };
+
+ static const Field sFields[20];
+
+ size_t& ValueForField(const Field& aField) {
+ char* fieldPtr = reinterpret_cast<char*>(this) + aField.mOffset;
+ return reinterpret_cast<size_t*>(fieldPtr)[0];
+ }
+ size_t ValueForField(const Field& aField) const {
+ return const_cast<MemoryMapping*>(this)->ValueForField(aField);
+ }
+};
+
+nsresult GetMemoryMappings(nsTArray<MemoryMapping>& aMappings, pid_t aPid = 0);
+
+} // namespace mozilla
+
+#endif // mozilla_MemoryMapping_h
diff --git a/xpcom/base/MemoryPressureLevelMac.h b/xpcom/base/MemoryPressureLevelMac.h
new file mode 100644
index 0000000000..38cb49a4fd
--- /dev/null
+++ b/xpcom/base/MemoryPressureLevelMac.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MemoryPressureLevelMac_h
+#define mozilla_MemoryPressureLevelMac_h
+
+namespace mozilla {
+
+#if defined(XP_DARWIN)
+// An internal representation of the Mac memory-pressure level constants.
+class MacMemoryPressureLevel {
+ public:
+ // Order enum values so that higher integer values represent higher
+ // memory pressure levels allowing comparison operators to be used.
+ enum class Value {
+ eUnset,
+ eUnexpected,
+ eNormal,
+ eWarning,
+ eCritical,
+ };
+
+ MacMemoryPressureLevel() : mValue(Value::eUnset) {}
+ MOZ_IMPLICIT MacMemoryPressureLevel(Value aValue) : mValue(aValue) {}
+
+ bool operator==(const Value& aRhsValue) const { return mValue == aRhsValue; }
+ bool operator==(const MacMemoryPressureLevel& aRhs) const {
+ return mValue == aRhs.mValue;
+ }
+
+ // Implement '<' and derive the other comparators from it.
+ bool operator<(const MacMemoryPressureLevel& aRhs) const {
+ return mValue < aRhs.mValue;
+ }
+ bool operator>(const MacMemoryPressureLevel& aRhs) const {
+ return aRhs < *this;
+ }
+ bool operator<=(const MacMemoryPressureLevel& aRhs) const {
+ return !(aRhs < *this);
+ }
+ bool operator>=(const MacMemoryPressureLevel& aRhs) const {
+ return !(*this < aRhs);
+ }
+
+ Value GetValue() { return mValue; }
+ bool IsNormal() { return mValue == Value::eNormal; }
+ bool IsUnsetOrNormal() { return IsNormal() || (mValue == Value::eUnset); }
+ bool IsWarningOrAbove() {
+ return (mValue == Value::eWarning) || (mValue == Value::eCritical);
+ }
+
+ const char* ToString() {
+ switch (mValue) {
+ case Value::eUnset:
+ return "Unset";
+ case Value::eUnexpected:
+ return "Unexpected";
+ case Value::eNormal:
+ return "Normal";
+ case Value::eWarning:
+ return "Warning";
+ case Value::eCritical:
+ return "Critical";
+ }
+ }
+
+ private:
+ Value mValue;
+};
+#endif
+
+} // namespace mozilla
+
+#endif // mozilla_MemoryPressureLevelMac_h
diff --git a/xpcom/base/MemoryReportingProcess.h b/xpcom/base/MemoryReportingProcess.h
new file mode 100644
index 0000000000..36410e702b
--- /dev/null
+++ b/xpcom/base/MemoryReportingProcess.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef xpcom_base_MemoryReportingProcess_h
+#define xpcom_base_MemoryReportingProcess_h
+
+#include <stdint.h>
+#include "nscore.h"
+
+namespace mozilla {
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+template <class T>
+class Maybe;
+
+// Top-level process actors should implement this to integrate with
+// nsMemoryReportManager.
+class MemoryReportingProcess {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
+
+ virtual ~MemoryReportingProcess() = default;
+
+ // Return true if the process is still alive, false otherwise.
+ virtual bool IsAlive() const = 0;
+
+ // Initiate a memory report request, returning true if a report was
+ // successfully initiated and false otherwise.
+ virtual bool SendRequestMemoryReport(
+ const uint32_t& aGeneration, const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile) = 0;
+
+ virtual int32_t Pid() const = 0;
+};
+
+} // namespace mozilla
+
+#endif // xpcom_base_MemoryReportingProcess_h
diff --git a/xpcom/base/MemoryTelemetry.cpp b/xpcom/base/MemoryTelemetry.cpp
new file mode 100644
index 0000000000..d89c528049
--- /dev/null
+++ b/xpcom/base/MemoryTelemetry.cpp
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryTelemetry.h"
+#include "nsMemoryReporterManager.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#ifdef MOZ_PHC
+# include "mozilla/PHCManager.h"
+#endif
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Services.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/SimpleEnumerator.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIMemoryReporter.h"
+#include "nsIWindowMediator.h"
+#include "nsImportModule.h"
+#include "nsITelemetry.h"
+#include "nsNetCID.h"
+#include "nsObserverService.h"
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "xpcpublic.h"
+
+#include <cstdlib>
+
+using namespace mozilla;
+
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::ContentParent;
+
+// Do not gather data more than once a minute (ms)
+static constexpr uint32_t kTelemetryInterval = 60 * 1000;
+
+static constexpr const char* kTopicCycleCollectorBegin =
+ "cycle-collector-begin";
+
+namespace {
+
+enum class PrevValue : uint32_t {
+#ifdef XP_WIN
+ LOW_MEMORY_EVENTS_VIRTUAL,
+ LOW_MEMORY_EVENTS_COMMIT_SPACE,
+ LOW_MEMORY_EVENTS_PHYSICAL,
+#endif
+#if defined(XP_LINUX) && !defined(ANDROID)
+ PAGE_FAULTS_HARD,
+#endif
+ SIZE_,
+};
+
+} // anonymous namespace
+
+constexpr uint32_t kUninitialized = ~0;
+
+static uint32_t gPrevValues[uint32_t(PrevValue::SIZE_)];
+
+static uint32_t PrevValueIndex(Telemetry::HistogramID aId) {
+ switch (aId) {
+#ifdef XP_WIN
+ case Telemetry::LOW_MEMORY_EVENTS_VIRTUAL:
+ return uint32_t(PrevValue::LOW_MEMORY_EVENTS_VIRTUAL);
+ case Telemetry::LOW_MEMORY_EVENTS_COMMIT_SPACE:
+ return uint32_t(PrevValue::LOW_MEMORY_EVENTS_COMMIT_SPACE);
+ case Telemetry::LOW_MEMORY_EVENTS_PHYSICAL:
+ return uint32_t(PrevValue::LOW_MEMORY_EVENTS_PHYSICAL);
+#endif
+#if defined(XP_LINUX) && !defined(ANDROID)
+ case Telemetry::PAGE_FAULTS_HARD:
+ return uint32_t(PrevValue::PAGE_FAULTS_HARD);
+#endif
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected histogram ID");
+ return 0;
+ }
+}
+
+NS_IMPL_ISUPPORTS(MemoryTelemetry, nsIObserver, nsISupportsWeakReference)
+
+MemoryTelemetry::MemoryTelemetry()
+ : mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)) {}
+
+void MemoryTelemetry::Init() {
+ for (auto& val : gPrevValues) {
+ val = kUninitialized;
+ }
+
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ MOZ_RELEASE_ASSERT(obs);
+
+ obs->AddObserver(this, "content-child-shutdown", true);
+ }
+}
+
+/* static */ MemoryTelemetry& MemoryTelemetry::Get() {
+ static RefPtr<MemoryTelemetry> sInstance;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sInstance) {
+ sInstance = new MemoryTelemetry();
+ sInstance->Init();
+ ClearOnShutdown(&sInstance);
+ }
+ return *sInstance;
+}
+
+nsresult MemoryTelemetry::DelayedInit() {
+ if (Telemetry::CanRecordExtended()) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ MOZ_RELEASE_ASSERT(obs);
+
+ obs->AddObserver(this, kTopicCycleCollectorBegin, true);
+ }
+
+ GatherReports();
+
+ return NS_OK;
+}
+
+nsresult MemoryTelemetry::Shutdown() {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ MOZ_RELEASE_ASSERT(obs);
+
+ obs->RemoveObserver(this, kTopicCycleCollectorBegin);
+
+ return NS_OK;
+}
+
+static inline void HandleMemoryReport(Telemetry::HistogramID aId,
+ int32_t aUnits, uint64_t aAmount,
+ const nsCString& aKey = VoidCString()) {
+ uint32_t val;
+ switch (aUnits) {
+ case nsIMemoryReporter::UNITS_BYTES:
+ val = uint32_t(aAmount / 1024);
+ break;
+
+ case nsIMemoryReporter::UNITS_PERCENTAGE:
+ // UNITS_PERCENTAGE amounts are 100x greater than their raw value.
+ val = uint32_t(aAmount / 100);
+ break;
+
+ case nsIMemoryReporter::UNITS_COUNT:
+ val = uint32_t(aAmount);
+ break;
+
+ case nsIMemoryReporter::UNITS_COUNT_CUMULATIVE: {
+ // If the reporter gives us a cumulative count, we'll report the
+ // difference in its value between now and our previous ping.
+
+ uint32_t idx = PrevValueIndex(aId);
+ uint32_t prev = gPrevValues[idx];
+ gPrevValues[idx] = aAmount;
+
+ if (prev == kUninitialized) {
+ // If this is the first time we're reading this reporter, store its
+ // current value but don't report it in the telemetry ping, so we
+ // ignore the effect startup had on the reporter.
+ return;
+ }
+ val = aAmount - prev;
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected aUnits value");
+ return;
+ }
+
+ // Note: The reference equality check here should allow the compiler to
+ // optimize this case out at compile time when we weren't given a key,
+ // while IsEmpty() or IsVoid() most likely will not.
+ if (&aKey == &VoidCString()) {
+ Telemetry::Accumulate(aId, val);
+ } else {
+ Telemetry::Accumulate(aId, aKey, val);
+ }
+}
+
+nsresult MemoryTelemetry::GatherReports(
+ const std::function<void()>& aCompletionCallback) {
+ auto cleanup = MakeScopeExit([&]() {
+ if (aCompletionCallback) {
+ aCompletionCallback();
+ }
+ });
+
+ RefPtr<nsMemoryReporterManager> mgr = nsMemoryReporterManager::GetOrCreate();
+ MOZ_DIAGNOSTIC_ASSERT(mgr);
+ NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
+
+#define RECORD(id, metric, units) \
+ do { \
+ int64_t amt; \
+ nsresult rv = mgr->Get##metric(&amt); \
+ if (NS_SUCCEEDED(rv)) { \
+ HandleMemoryReport(Telemetry::id, nsIMemoryReporter::units, amt); \
+ } else if (rv != NS_ERROR_NOT_AVAILABLE) { \
+ NS_WARNING("Failed to retrieve memory telemetry for " #metric); \
+ } \
+ } while (0)
+
+ // GHOST_WINDOWS is opt-out as of Firefox 55
+ RECORD(GHOST_WINDOWS, GhostWindows, UNITS_COUNT);
+
+ // If we're running in the parent process, collect data from all processes for
+ // the MEMORY_TOTAL histogram.
+ if (XRE_IsParentProcess() && !mGatheringTotalMemory) {
+ GatherTotalMemory();
+ }
+
+ if (!Telemetry::CanRecordReleaseData()) {
+ return NS_OK;
+ }
+
+ // Get memory measurements from distinguished amount attributes. We used
+ // to measure "explicit" too, but it could cause hangs, and the data was
+ // always really noisy anyway. See bug 859657.
+ //
+ // test_TelemetrySession.js relies on some of these histograms being
+ // here. If you remove any of the following histograms from here, you'll
+ // have to modify test_TelemetrySession.js:
+ //
+ // * MEMORY_TOTAL,
+ // * MEMORY_JS_GC_HEAP, and
+ // * MEMORY_JS_COMPARTMENTS_SYSTEM.
+ //
+ // The distinguished amount attribute names don't match the telemetry id
+ // names in some cases due to a combination of (a) historical reasons, and
+ // (b) the fact that we can't change telemetry id names without breaking
+ // data continuity.
+
+ // Collect cheap or main-thread only metrics synchronously, on the main
+ // thread.
+ RECORD(MEMORY_JS_GC_HEAP, JSMainRuntimeGCHeap, UNITS_BYTES);
+ RECORD(MEMORY_JS_COMPARTMENTS_SYSTEM, JSMainRuntimeCompartmentsSystem,
+ UNITS_COUNT);
+ RECORD(MEMORY_JS_COMPARTMENTS_USER, JSMainRuntimeCompartmentsUser,
+ UNITS_COUNT);
+ RECORD(MEMORY_JS_REALMS_SYSTEM, JSMainRuntimeRealmsSystem, UNITS_COUNT);
+ RECORD(MEMORY_JS_REALMS_USER, JSMainRuntimeRealmsUser, UNITS_COUNT);
+ RECORD(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, ImagesContentUsedUncompressed,
+ UNITS_BYTES);
+ RECORD(MEMORY_STORAGE_SQLITE, StorageSQLite, UNITS_BYTES);
+#ifdef XP_WIN
+ RECORD(LOW_MEMORY_EVENTS_PHYSICAL, LowMemoryEventsPhysical,
+ UNITS_COUNT_CUMULATIVE);
+#endif
+#if defined(XP_LINUX) && !defined(ANDROID)
+ RECORD(PAGE_FAULTS_HARD, PageFaultsHard, UNITS_COUNT_CUMULATIVE);
+#endif
+
+#ifdef HAVE_JEMALLOC_STATS
+ jemalloc_stats_t stats;
+ jemalloc_stats(&stats);
+ HandleMemoryReport(Telemetry::MEMORY_HEAP_ALLOCATED,
+ nsIMemoryReporter::UNITS_BYTES, mgr->HeapAllocated(stats));
+ HandleMemoryReport(Telemetry::MEMORY_HEAP_OVERHEAD_FRACTION,
+ nsIMemoryReporter::UNITS_PERCENTAGE,
+ mgr->HeapOverheadFraction(stats));
+#endif
+
+#ifdef MOZ_PHC
+ ReportPHCTelemetry();
+#endif
+
+ RefPtr<Runnable> completionRunnable;
+ if (aCompletionCallback) {
+ completionRunnable = NS_NewRunnableFunction(__func__, aCompletionCallback);
+ }
+
+ // Collect expensive metrics that can be calculated off-main-thread
+ // asynchronously, on a background thread.
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "MemoryTelemetry::GatherReports", [mgr, completionRunnable]() mutable {
+ Telemetry::AutoTimer<Telemetry::MEMORY_COLLECTION_TIME> autoTimer;
+ RECORD(MEMORY_VSIZE, Vsize, UNITS_BYTES);
+#if !defined(HAVE_64BIT_BUILD) || !defined(XP_WIN)
+ RECORD(MEMORY_VSIZE_MAX_CONTIGUOUS, VsizeMaxContiguous, UNITS_BYTES);
+#endif
+ RECORD(MEMORY_RESIDENT_FAST, ResidentFast, UNITS_BYTES);
+ RECORD(MEMORY_RESIDENT_PEAK, ResidentPeak, UNITS_BYTES);
+// Although we can measure unique memory on MacOS we choose not to, because
+// doing so is too slow for telemetry.
+#ifndef XP_MACOSX
+ RECORD(MEMORY_UNIQUE, ResidentUnique, UNITS_BYTES);
+#endif
+
+ if (completionRunnable) {
+ NS_DispatchToMainThread(completionRunnable.forget(),
+ NS_DISPATCH_NORMAL);
+ }
+ });
+
+#undef RECORD
+
+ nsresult rv = mThreadPool->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ cleanup.release();
+ }
+
+ return NS_OK;
+}
+
+namespace {
+struct ChildProcessInfo {
+ GeckoProcessType mType;
+#if defined(XP_WIN)
+ HANDLE mHandle;
+#elif defined(XP_MACOSX)
+ task_t mHandle;
+#else
+ pid_t mHandle;
+#endif
+};
+} // namespace
+
+/**
+ * Runs a task on the background thread pool to fetch the memory usage of all
+ * processes.
+ */
+void MemoryTelemetry::GatherTotalMemory() {
+ MOZ_ASSERT(!mGatheringTotalMemory);
+ mGatheringTotalMemory = true;
+
+ nsTArray<ChildProcessInfo> infos;
+ mozilla::ipc::GeckoChildProcessHost::GetAll(
+ [&](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
+ if (!aGeckoProcess->GetChildProcessHandle()) {
+ return;
+ }
+
+ ChildProcessInfo info{};
+ info.mType = aGeckoProcess->GetProcessType();
+
+ // NOTE: For now we ignore non-content processes here for compatibility
+ // with the existing probe. We may want to introduce a new probe in the
+ // future which also collects data for non-content processes.
+ if (info.mType != GeckoProcessType_Content) {
+ return;
+ }
+
+#if defined(XP_WIN)
+ if (!::DuplicateHandle(::GetCurrentProcess(),
+ aGeckoProcess->GetChildProcessHandle(),
+ ::GetCurrentProcess(), &info.mHandle, 0, false,
+ DUPLICATE_SAME_ACCESS)) {
+ return;
+ }
+#elif defined(XP_MACOSX)
+ info.mHandle = aGeckoProcess->GetChildTask();
+ if (mach_port_mod_refs(mach_task_self(), info.mHandle,
+ MACH_PORT_RIGHT_SEND, 1) != KERN_SUCCESS) {
+ return;
+ }
+#else
+ info.mHandle = aGeckoProcess->GetChildProcessId();
+#endif
+
+ infos.AppendElement(info);
+ });
+
+ mThreadPool->Dispatch(NS_NewRunnableFunction(
+ "MemoryTelemetry::GatherTotalMemory", [infos = std::move(infos)] {
+ RefPtr<nsMemoryReporterManager> mgr =
+ nsMemoryReporterManager::GetOrCreate();
+ MOZ_RELEASE_ASSERT(mgr);
+
+ int64_t totalMemory = mgr->ResidentFast();
+ nsTArray<int64_t> childSizes(infos.Length());
+
+ // Use our handle for the remote process to collect resident unique set
+ // size information for that process.
+ for (const auto& info : infos) {
+#ifdef XP_MACOSX
+ int64_t memory =
+ nsMemoryReporterManager::PhysicalFootprint(info.mHandle);
+#else
+ int64_t memory =
+ nsMemoryReporterManager::ResidentUnique(info.mHandle);
+#endif
+ if (memory > 0) {
+ childSizes.AppendElement(memory);
+ totalMemory += memory;
+ }
+
+#if defined(XP_WIN)
+ ::CloseHandle(info.mHandle);
+#elif defined(XP_MACOSX)
+ mach_port_deallocate(mach_task_self(), info.mHandle);
+#endif
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MemoryTelemetry::FinishGatheringTotalMemory",
+ [totalMemory, childSizes = std::move(childSizes)] {
+ MemoryTelemetry::Get().FinishGatheringTotalMemory(totalMemory,
+ childSizes);
+ }));
+ }));
+}
+
+nsresult MemoryTelemetry::FinishGatheringTotalMemory(
+ int64_t aTotalMemory, const nsTArray<int64_t>& aChildSizes) {
+ mGatheringTotalMemory = false;
+
+ // Total memory usage can be difficult to measure both accurately and fast
+ // enough for telemetry (iterating memory maps can jank whole processes on
+ // MacOS). Therefore this shouldn't be relied on as an absolute measurement
+ // especially on MacOS where it double-counts shared memory. For a more
+ // detailed explaination see:
+ // https://groups.google.com/a/mozilla.org/g/dev-platform/c/WGNOtjHdsdA
+ HandleMemoryReport(Telemetry::MEMORY_TOTAL, nsIMemoryReporter::UNITS_BYTES,
+ aTotalMemory);
+
+ if (aChildSizes.Length() > 1) {
+ int32_t tabsCount;
+ MOZ_TRY_VAR(tabsCount, GetOpenTabsCount());
+
+ nsCString key;
+ if (tabsCount <= 10) {
+ key = "0 - 10 tabs";
+ } else if (tabsCount <= 500) {
+ key = "11 - 500 tabs";
+ } else {
+ key = "more tabs";
+ }
+
+ // Mean of the USS of all the content processes.
+ int64_t mean = 0;
+ for (auto size : aChildSizes) {
+ mean += size;
+ }
+ mean /= aChildSizes.Length();
+
+ // For some users, for unknown reasons (though most likely because they're
+ // in a sandbox without procfs mounted), we wind up with 0 here, which
+ // triggers a floating point exception if we try to calculate values using
+ // it.
+ if (!mean) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Absolute error of USS for each content process, normalized by the mean
+ // (*100 to get it in percentage). 20% means for a content process that it
+ // is using 20% more or 20% less than the mean.
+ for (auto size : aChildSizes) {
+ int64_t diff = llabs(size - mean) * 100 / mean;
+
+ HandleMemoryReport(Telemetry::MEMORY_DISTRIBUTION_AMONG_CONTENT,
+ nsIMemoryReporter::UNITS_COUNT, diff, key);
+ }
+ }
+
+ // This notification is for testing only.
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyObservers(nullptr, "gather-memory-telemetry-finished", nullptr);
+ }
+
+ return NS_OK;
+}
+
+/* static */ Result<uint32_t, nsresult> MemoryTelemetry::GetOpenTabsCount() {
+ nsresult rv;
+
+ nsCOMPtr<nsIWindowMediator> windowMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ MOZ_TRY(rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ MOZ_TRY(windowMediator->GetEnumerator(u"navigator:browser",
+ getter_AddRefs(enumerator)));
+
+ uint32_t total = 0;
+ for (const auto& window : SimpleEnumerator<nsPIDOMWindowOuter>(enumerator)) {
+ nsCOMPtr<nsIBrowserDOMWindow> browserWin =
+ nsGlobalWindowOuter::Cast(window)->GetBrowserDOMWindow();
+
+ NS_ENSURE_TRUE(browserWin, Err(NS_ERROR_UNEXPECTED));
+
+ uint32_t tabCount;
+ MOZ_TRY(browserWin->GetTabCount(&tabCount));
+ total += tabCount;
+ }
+
+ return total;
+}
+
+nsresult MemoryTelemetry::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, kTopicCycleCollectorBegin) == 0) {
+ auto now = TimeStamp::Now();
+ if (!mLastPoll.IsNull() &&
+ (now - mLastPoll).ToMilliseconds() < kTelemetryInterval) {
+ return NS_OK;
+ }
+
+ mLastPoll = now;
+
+ NS_DispatchToCurrentThreadQueue(
+ NewRunnableMethod<std::function<void()>>(
+ "MemoryTelemetry::GatherReports", this,
+ &MemoryTelemetry::GatherReports, nullptr),
+ EventQueuePriority::Idle);
+ } else if (strcmp(aTopic, "content-child-shutdown") == 0) {
+ if (nsCOMPtr<nsITelemetry> telemetry =
+ do_GetService("@mozilla.org/base/telemetry;1")) {
+ telemetry->FlushBatchedChildTelemetry();
+ }
+ }
+ return NS_OK;
+}
diff --git a/xpcom/base/MemoryTelemetry.h b/xpcom/base/MemoryTelemetry.h
new file mode 100644
index 0000000000..b7c7fe8ad6
--- /dev/null
+++ b/xpcom/base/MemoryTelemetry.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MemoryTelemetry_h
+#define mozilla_MemoryTelemetry_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Result.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+#include <functional>
+
+class nsIEventTarget;
+
+namespace mozilla {
+
+namespace ipc {
+enum class ResponseRejectReason;
+}
+
+/**
+ * Periodically gathers memory usage metrics after cycle collection, and
+ * populates telemetry histograms with their values.
+ */
+class MemoryTelemetry final : public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static MemoryTelemetry& Get();
+
+ nsresult GatherReports(
+ const std::function<void()>& aCompletionCallback = nullptr);
+
+ /**
+ * Does expensive initialization, which should happen only after startup has
+ * completed, and the event loop is idle.
+ */
+ nsresult DelayedInit();
+
+ nsresult Shutdown();
+
+ private:
+ MemoryTelemetry();
+
+ ~MemoryTelemetry() = default;
+
+ void Init();
+
+ static Result<uint32_t, nsresult> GetOpenTabsCount();
+
+ void GatherTotalMemory();
+ nsresult FinishGatheringTotalMemory(int64_t aTotalMemory,
+ const nsTArray<int64_t>& aChildSizes);
+
+ nsCOMPtr<nsIEventTarget> mThreadPool;
+
+ bool mGatheringTotalMemory = false;
+
+ TimeStamp mLastPoll{};
+};
+
+} // namespace mozilla
+
+#endif // defined mozilla_MemoryTelemetry_h
diff --git a/xpcom/base/NSPRLogModulesParser.cpp b/xpcom/base/NSPRLogModulesParser.cpp
new file mode 100644
index 0000000000..44fb50dbc7
--- /dev/null
+++ b/xpcom/base/NSPRLogModulesParser.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NSPRLogModulesParser.h"
+
+#include "mozilla/Tokenizer.h"
+
+const char kDelimiters[] = ", ";
+const char kAdditionalWordChars[] = "_-.*";
+
+namespace mozilla {
+
+void NSPRLogModulesParser(
+ const char* aLogModules,
+ const std::function<void(const char*, LogLevel, int32_t)>& aCallback) {
+ if (!aLogModules) {
+ return;
+ }
+
+ Tokenizer parser(aLogModules, kDelimiters, kAdditionalWordChars);
+ nsAutoCString moduleName;
+
+ Tokenizer::Token rustModSep =
+ parser.AddCustomToken("::", Tokenizer::CASE_SENSITIVE);
+
+ auto readModuleName = [&](nsAutoCString& moduleName) -> bool {
+ moduleName.Truncate();
+ nsDependentCSubstring sub;
+ parser.Record();
+ // If the name doesn't include at least one word, we've reached the end.
+ if (!parser.ReadWord(sub)) {
+ return false;
+ }
+ // We will exit this loop if when any of the condition fails
+ while (parser.Check(rustModSep) && parser.ReadWord(sub)) {
+ }
+ // Claim will include characters of the last sucessfully read item
+ parser.Claim(moduleName, Tokenizer::INCLUDE_LAST);
+ return true;
+ };
+
+ // Format: LOG_MODULES="Foo:2,Bar, Baz:5,rust_crate::mod::file:5"
+ while (readModuleName(moduleName)) {
+ // Next should be :<level>, default to Error if not provided.
+ LogLevel logLevel = LogLevel::Error;
+ int32_t levelValue = 0;
+ if (parser.CheckChar(':')) {
+ // NB: If a level isn't provided after the ':' we assume the default
+ // Error level is desired. This differs from NSPR which will stop
+ // processing the log module string in this case.
+ if (parser.ReadSignedInteger(&levelValue)) {
+ logLevel = ToLogLevel(levelValue);
+ }
+ }
+
+ aCallback(moduleName.get(), logLevel, levelValue);
+
+ // Skip ahead to the next token.
+ parser.SkipWhites();
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/NSPRLogModulesParser.h b/xpcom/base/NSPRLogModulesParser.h
new file mode 100644
index 0000000000..2aa6571e65
--- /dev/null
+++ b/xpcom/base/NSPRLogModulesParser.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include <functional>
+
+namespace mozilla {
+
+/**
+ * Helper function that parses the legacy NSPR_LOG_MODULES env var format
+ * for specifying log levels and logging options.
+ *
+ * @param aLogModules The log modules configuration string.
+ * @param aCallback The callback to invoke for each log module config entry.
+ */
+void NSPRLogModulesParser(
+ const char* aLogModules,
+ const std::function<void(const char*, LogLevel, int32_t)>& aCallback);
+
+} // namespace mozilla
diff --git a/xpcom/base/OwningNonNull.h b/xpcom/base/OwningNonNull.h
new file mode 100644
index 0000000000..24c22f8f60
--- /dev/null
+++ b/xpcom/base/OwningNonNull.h
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A class for non-null strong pointers to reference-counted objects. */
+
+#ifndef mozilla_OwningNonNull_h
+#define mozilla_OwningNonNull_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionNoteChild.h"
+
+namespace mozilla {
+
+// OwningNonNull<T> is similar to a RefPtr<T>, which is not null after initial
+// initialization. It has a restricted interface compared to RefPtr, with some
+// additional operations defined. The main use is in DOM bindings. Use it
+// outside DOM bindings only if you can ensure it never escapes without being
+// properly initialized, and you don't need to move it. Otherwise, use a
+// RefPtr<T> instead.
+//
+// Compared to a plain RefPtr<T>, in particular
+// - it is copyable but not movable
+// - it can be constructed and assigned from T& and is convertible to T&
+// implicitly
+// - it cannot be cleared by the user once initialized, though it can be
+// re-assigned a new (non-null) value
+// - it is not convertible to bool, but there is an explicit isInitialized
+// member function
+//
+// Beware that there are two cases where an OwningNonNull<T> actually is nullptr
+// - it was default-constructed and not yet initialized
+// - it was cleared during CC unlinking.
+// All attempts to use it in an invalid state will trigger an assertion in debug
+// builds.
+//
+// The original intent of OwningNonNull<T> was to implement a class with the
+// same auto-conversion and annotation semantics as mozilla::dom::NonNull<T>
+// (i.e. never null once you have properly initialized it, auto-converts to T&),
+// but that holds a strong reference to the object involved. This was designed
+// for use in DOM bindings and in particular for storing what WebIDL represents
+// as InterfaceName (as opposed to `InterfaceName?`) in various containers
+// (dictionaries, sequences). DOM bindings never allow a default-constructed
+// uninitialized OwningNonNull to escape. RefPtr could have been used for this
+// use case, just like we could have used T* instead of NonNull<T>, but it
+// seemed desirable to explicitly annotate the non-null nature of the things
+// involved to eliminate pointless null-checks, which otherwise tend to
+// proliferate.
+template <class T>
+class MOZ_IS_SMARTPTR_TO_REFCOUNTED OwningNonNull {
+ public:
+ using element_type = T;
+
+ OwningNonNull() = default;
+
+ MOZ_IMPLICIT OwningNonNull(T& aValue) { init(&aValue); }
+
+ template <class U>
+ MOZ_IMPLICIT OwningNonNull(already_AddRefed<U>&& aValue) {
+ init(aValue);
+ }
+
+ template <class U>
+ MOZ_IMPLICIT OwningNonNull(RefPtr<U>&& aValue) {
+ init(std::move(aValue));
+ }
+
+ template <class U>
+ MOZ_IMPLICIT OwningNonNull(const OwningNonNull<U>& aValue) {
+ init(aValue);
+ }
+
+ // This is no worse than get() in terms of const handling.
+ operator T&() const { return ref(); }
+
+ operator T*() const { return get(); }
+
+ // Conversion to bool is always true, so delete to catch errors
+ explicit operator bool() const = delete;
+
+ T* operator->() const { return get(); }
+
+ T& operator*() const { return ref(); }
+
+ OwningNonNull<T>& operator=(T* aValue) {
+ init(aValue);
+ return *this;
+ }
+
+ OwningNonNull<T>& operator=(T& aValue) {
+ init(&aValue);
+ return *this;
+ }
+
+ template <class U>
+ OwningNonNull<T>& operator=(already_AddRefed<U>&& aValue) {
+ init(aValue);
+ return *this;
+ }
+
+ template <class U>
+ OwningNonNull<T>& operator=(RefPtr<U>&& aValue) {
+ init(std::move(aValue));
+ return *this;
+ }
+
+ template <class U>
+ OwningNonNull<T>& operator=(const OwningNonNull<U>& aValue) {
+ init(aValue);
+ return *this;
+ }
+
+ // Don't allow assigning nullptr, it makes no sense
+ void operator=(decltype(nullptr)) = delete;
+
+ T& ref() const {
+ MOZ_ASSERT(mInited);
+ MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null");
+ return *mPtr;
+ }
+
+ // Make us work with smart pointer helpers that expect a get().
+ T* get() const {
+ MOZ_ASSERT(mInited);
+ MOZ_ASSERT(mPtr, "OwningNonNull<T> was set to null");
+ return mPtr;
+ }
+
+ template <typename U>
+ void swap(U& aOther) {
+ mPtr.swap(aOther);
+#ifdef DEBUG
+ mInited = mPtr;
+#endif
+ }
+
+ // We have some consumers who want to check whether we're inited in non-debug
+ // builds as well. Luckily, we have the invariant that we're inited precisely
+ // when mPtr is non-null.
+ bool isInitialized() const {
+ MOZ_ASSERT(!!mPtr == mInited, "mInited out of sync with mPtr?");
+ return mPtr;
+ }
+
+ private:
+ void unlinkForCC() {
+#ifdef DEBUG
+ mInited = false;
+#endif
+ mPtr = nullptr;
+ }
+
+ // Allow ImplCycleCollectionUnlink to call unlinkForCC().
+ template <typename U>
+ friend void ImplCycleCollectionUnlink(OwningNonNull<U>& aField);
+
+ protected:
+ template <typename U>
+ void init(U&& aValue) {
+ mPtr = std::move(aValue);
+ MOZ_ASSERT(mPtr);
+#ifdef DEBUG
+ mInited = true;
+#endif
+ }
+
+ RefPtr<T> mPtr;
+#ifdef DEBUG
+ bool mInited = false;
+#endif
+};
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(OwningNonNull<T>& aField) {
+ aField.unlinkForCC();
+}
+
+template <typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, OwningNonNull<T>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags);
+}
+
+} // namespace mozilla
+
+// Declared in nsCOMPtr.h
+template <class T>
+template <class U>
+nsCOMPtr<T>::nsCOMPtr(const mozilla::OwningNonNull<U>& aOther)
+ : nsCOMPtr(aOther.get()) {}
+
+template <class T>
+template <class U>
+nsCOMPtr<T>& nsCOMPtr<T>::operator=(const mozilla::OwningNonNull<U>& aOther) {
+ return operator=(aOther.get());
+}
+
+// Declared in mozilla/RefPtr.h
+template <class T>
+template <class U>
+RefPtr<T>::RefPtr(const mozilla::OwningNonNull<U>& aOther)
+ : RefPtr(aOther.get()) {}
+
+template <class T>
+template <class U>
+RefPtr<T>& RefPtr<T>::operator=(const mozilla::OwningNonNull<U>& aOther) {
+ return operator=(aOther.get());
+}
+
+#endif // mozilla_OwningNonNull_h
diff --git a/xpcom/base/PHCManager.cpp b/xpcom/base/PHCManager.cpp
new file mode 100644
index 0000000000..f8124312f5
--- /dev/null
+++ b/xpcom/base/PHCManager.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PHCManager.h"
+
+#include "PHC.h"
+#include "mozilla/Literals.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_memory.h"
+#include "mozilla/Telemetry.h"
+#include "prsystem.h"
+
+namespace mozilla {
+
+using namespace phc;
+
+static const char kPHCEnabledPref[] = "memory.phc.enabled";
+static const char kPHCMinRamMBPref[] = "memory.phc.min_ram_mb";
+static const char kPHCAvgDelayFirst[] = "memory.phc.avg_delay.first";
+static const char kPHCAvgDelayNormal[] = "memory.phc.avg_delay.normal";
+static const char kPHCAvgDelayPageRuse[] = "memory.phc.avg_delay.page_reuse";
+
+// True if PHC has ever been enabled for this process.
+static bool sWasPHCEnabled = false;
+
+static void UpdatePHCState() {
+ size_t mem_size = PR_GetPhysicalMemorySize() / (1_MiB);
+ size_t min_mem_size = StaticPrefs::memory_phc_min_ram_mb();
+
+ // Only enable PHC if there are at least 8GB of ram. Note that we use
+ // 1000 bytes per kilobyte rather than 1024. Some 8GB machines will have
+ // slightly lower actual RAM available after some hardware devices
+ // reserve some.
+ if (StaticPrefs::memory_phc_enabled() && mem_size >= min_mem_size) {
+ // Set PHC probablities before enabling PHC so that the first allocation
+ // delay gets used.
+ SetPHCProbabilities(StaticPrefs::memory_phc_avg_delay_first(),
+ StaticPrefs::memory_phc_avg_delay_normal(),
+ StaticPrefs::memory_phc_avg_delay_page_reuse());
+
+ SetPHCState(Enabled);
+ sWasPHCEnabled = true;
+ } else {
+ SetPHCState(OnlyFree);
+ }
+}
+
+static void PrefChangeCallback(const char* aPrefName, void* aNull) {
+ MOZ_ASSERT((0 == strcmp(aPrefName, kPHCEnabledPref)) ||
+ (0 == strcmp(aPrefName, kPHCMinRamMBPref)) ||
+ (0 == strcmp(aPrefName, kPHCAvgDelayFirst)) ||
+ (0 == strcmp(aPrefName, kPHCAvgDelayNormal)) ||
+ (0 == strcmp(aPrefName, kPHCAvgDelayPageRuse)));
+
+ UpdatePHCState();
+}
+
+void InitPHCState() {
+ Preferences::RegisterCallback(PrefChangeCallback, kPHCEnabledPref);
+ Preferences::RegisterCallback(PrefChangeCallback, kPHCMinRamMBPref);
+ Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayFirst);
+ Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayNormal);
+ Preferences::RegisterCallback(PrefChangeCallback, kPHCAvgDelayPageRuse);
+ UpdatePHCState();
+}
+
+void ReportPHCTelemetry() {
+ if (!sWasPHCEnabled) {
+ return;
+ }
+
+ MemoryUsage usage;
+ PHCMemoryUsage(usage);
+
+ Accumulate(Telemetry::MEMORY_PHC_SLOP, usage.mFragmentationBytes);
+
+ PHCStats stats;
+ GetPHCStats(stats);
+
+ Accumulate(Telemetry::MEMORY_PHC_SLOTS_ALLOCATED, stats.mSlotsAllocated);
+ Accumulate(Telemetry::MEMORY_PHC_SLOTS_FREED, stats.mSlotsFreed);
+ // There are also slots that are unused (neither free nor allocated) they
+ // can be calculated by knowing the total number of slots.
+}
+
+}; // namespace mozilla
diff --git a/xpcom/base/PHCManager.h b/xpcom/base/PHCManager.h
new file mode 100644
index 0000000000..f1982c54eb
--- /dev/null
+++ b/xpcom/base/PHCManager.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_PHCManager_h
+#define mozilla_PHCManager_h
+
+namespace mozilla {
+
+// Read the PHC pref and potentially initialise PHC. Also register a
+// callback for the pref to update PHC as the pref changes.
+void InitPHCState();
+
+void ReportPHCTelemetry();
+
+}; // namespace mozilla
+
+#endif // mozilla_PHCManager_h
diff --git a/xpcom/base/RLBoxSandboxPool.cpp b/xpcom/base/RLBoxSandboxPool.cpp
new file mode 100644
index 0000000000..c3b86f1698
--- /dev/null
+++ b/xpcom/base/RLBoxSandboxPool.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/RLBoxSandboxPool.h"
+#ifdef MOZ_USING_WASM_SANDBOXING
+# include "wasm2c_rt_mem.h"
+#endif
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(RLBoxSandboxPool, nsITimerCallback, nsINamed)
+
+void RLBoxSandboxPool::StartTimer() {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mTimer, "timer already initialized");
+ if (NS_IsMainThread() &&
+ PastShutdownPhase(ShutdownPhase::AppShutdownConfirmed)) {
+ // If we're shutting down, setting the time might fail, and we don't need it
+ // (since all the memory will be cleaned up soon anyway). Note that
+ // PastShutdownPhase() can only be called on the main thread, but that's
+ // fine, because other threads will have joined already by the point timers
+ // start failing to register.
+ mPool.Clear();
+ return;
+ }
+ DebugOnly<nsresult> rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this, mDelaySeconds * 1000,
+ nsITimer::TYPE_ONE_SHOT, GetMainThreadSerialEventTarget());
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create timer");
+}
+
+void RLBoxSandboxPool::CancelTimer() {
+ mMutex.AssertCurrentThreadOwns();
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP RLBoxSandboxPool::Notify(nsITimer* aTimer) {
+ MutexAutoLock lock(mMutex);
+
+ mPool.Clear();
+ mTimer = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP RLBoxSandboxPool::GetName(nsACString& aName) {
+ aName.AssignLiteral("RLBoxSandboxPool");
+ return NS_OK;
+}
+
+void RLBoxSandboxPool::Push(UniquePtr<RLBoxSandboxDataBase> sbxData) {
+ MutexAutoLock lock(mMutex);
+
+ mPool.AppendElement(std::move(sbxData));
+ if (!mTimer) {
+ StartTimer();
+ }
+}
+
+UniquePtr<RLBoxSandboxPoolData> RLBoxSandboxPool::PopOrCreate(
+ uint64_t aMinSize) {
+ MutexAutoLock lock(mMutex);
+
+ UniquePtr<RLBoxSandboxDataBase> sbxData;
+
+ if (!mPool.IsEmpty()) {
+ const int64_t lastIndex = ReleaseAssertedCast<int64_t>(mPool.Length()) - 1;
+ for (int64_t i = lastIndex; i >= 0; i--) {
+ if (mPool[i]->mSize >= aMinSize) {
+ sbxData = std::move(mPool[i]);
+ mPool.RemoveElementAt(i);
+
+ // If we reuse a sandbox from the pool, reset the timer to clear the
+ // pool
+ CancelTimer();
+ if (!mPool.IsEmpty()) {
+ StartTimer();
+ }
+ break;
+ }
+ }
+ }
+
+ if (!sbxData) {
+#ifdef MOZ_USING_WASM_SANDBOXING
+ // RLBox's wasm sandboxes have a limited platform dependent capacity. We
+ // track this capacity in this pool.
+ const w2c_mem_capacity w2c_capacity = get_valid_wasm2c_memory_capacity(
+ aMinSize, true /* 32-bit wasm memory*/);
+ const uint64_t chosenCapacity = w2c_capacity.max_size;
+#else
+ // Note the noop sandboxes have no capacity limit. In this case we simply
+ // specify a value of 4gb. This is not actually enforced by the noop
+ // sandbox.
+ const uint64_t chosenCapacity = static_cast<uint64_t>(1) << 32;
+#endif
+ sbxData = CreateSandboxData(chosenCapacity);
+ NS_ENSURE_TRUE(sbxData, nullptr);
+ }
+
+ return MakeUnique<RLBoxSandboxPoolData>(std::move(sbxData), this);
+}
diff --git a/xpcom/base/RLBoxSandboxPool.h b/xpcom/base/RLBoxSandboxPool.h
new file mode 100644
index 0000000000..47f3893548
--- /dev/null
+++ b/xpcom/base/RLBoxSandboxPool.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SECURITY_RLBOX_SANDBOX_POOL_H_
+#define SECURITY_RLBOX_SANDBOX_POOL_H_
+
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsINamed.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/rlbox/rlbox_types.hpp"
+
+namespace mozilla {
+
+class RLBoxSandboxDataBase;
+class RLBoxSandboxPoolData;
+
+// The RLBoxSandboxPool class is used to manage a pool of sandboxes that are
+// reused -- to save sandbox creation time and memory -- and automatically
+// destroyed when no longer in used. The sandbox pool is threadsafe and can be
+// used to share unused sandboxes across a thread pool.
+//
+// Each sandbox pool manages a particular kind of sandbox (e.g., expat
+// sandboxes, woff2 sandboxes, etc.); this is largely because different
+// sandboxes might have different callbacks and attacker assumptions. Hence,
+// RLBoxSandboxPool is intended to be subclassed for the different kinds of
+// sandbox pools. Each sandbox pool class needs to implement the
+// CreateSandboxData() method, which returns a pointer to a RLBoxSandboxDataBase
+// object. RLBoxSandboxDataBase itself should be subclassed to implement
+// sandbox-specific details.
+class RLBoxSandboxPool : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ RLBoxSandboxPool(size_t aDelaySeconds = 10)
+ : mPool(),
+ mDelaySeconds(aDelaySeconds),
+ mMutex("RLBoxSandboxPool::mMutex"){};
+
+ void Push(UniquePtr<RLBoxSandboxDataBase> sbx);
+ // PopOrCreate returns a sandbox from the pool if the pool is not empty and
+ // tries to mint a new one otherwise. If creating a new sandbox fails, the
+ // function returns a nullptr. The parameter aMinSize is the minimum size of
+ // the sandbox memory.
+ UniquePtr<RLBoxSandboxPoolData> PopOrCreate(uint64_t aMinSize = 0);
+
+ protected:
+ // CreateSandboxData takes a parameter which is the size of the sandbox memory
+ virtual UniquePtr<RLBoxSandboxDataBase> CreateSandboxData(uint64_t aSize) = 0;
+ virtual ~RLBoxSandboxPool() = default;
+
+ private:
+ void StartTimer() MOZ_REQUIRES(mMutex);
+ void CancelTimer() MOZ_REQUIRES(mMutex);
+
+ nsTArray<UniquePtr<RLBoxSandboxDataBase>> mPool MOZ_GUARDED_BY(mMutex);
+ const size_t mDelaySeconds MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsITimer> mTimer MOZ_GUARDED_BY(mMutex);
+ mozilla::Mutex mMutex;
+};
+
+// The RLBoxSandboxDataBase class serves as the subclass for all sandbox data
+// classes, which keep track of the RLBox sandbox and any relevant sandbox data
+// (e.g., callbacks).
+class RLBoxSandboxDataBase {
+ public:
+ const uint64_t mSize;
+ explicit RLBoxSandboxDataBase(uint64_t aSize) : mSize(aSize) {}
+ virtual ~RLBoxSandboxDataBase() = default;
+};
+
+// This class is used wrap sandbox data objects (RLBoxSandboxDataBase) when they
+// are popped from sandbox pools. The wrapper destructor pushes the sandbox back
+// into the pool.
+class RLBoxSandboxPoolData {
+ public:
+ RLBoxSandboxPoolData(UniquePtr<RLBoxSandboxDataBase> aSbxData,
+ RefPtr<RLBoxSandboxPool> aPool) {
+ mSbxData = std::move(aSbxData);
+ mPool = aPool;
+ MOZ_COUNT_CTOR(RLBoxSandboxPoolData);
+ }
+
+ RLBoxSandboxDataBase* SandboxData() const { return mSbxData.get(); };
+
+ ~RLBoxSandboxPoolData() {
+ mPool->Push(std::move(mSbxData));
+ MOZ_COUNT_DTOR(RLBoxSandboxPoolData);
+ };
+
+ private:
+ UniquePtr<RLBoxSandboxDataBase> mSbxData;
+ RefPtr<RLBoxSandboxPool> mPool;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/RLBoxUtils.h b/xpcom/base/RLBoxUtils.h
new file mode 100644
index 0000000000..4a73affb63
--- /dev/null
+++ b/xpcom/base/RLBoxUtils.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SECURITY_RLBOX_UTILS_H_
+#define SECURITY_RLBOX_UTILS_H_
+
+#include "mozilla/rlbox/rlbox_types.hpp"
+
+namespace mozilla {
+
+/* The RLBoxTransferBufferToSandbox class is used to copy (or directly expose in
+ * the noop-sandbox case) buffers into the sandbox that are automatically freed
+ * when the RLBoxTransferBufferToSandbox is out of scope. NOTE: The sandbox
+ * lifetime must outlive all of its RLBoxTransferBufferToSandbox.
+ */
+template <typename T, typename S>
+class MOZ_STACK_CLASS RLBoxTransferBufferToSandbox {
+ public:
+ RLBoxTransferBufferToSandbox() = delete;
+ RLBoxTransferBufferToSandbox(rlbox::rlbox_sandbox<S>* aSandbox, const T* aBuf,
+ const size_t aLen)
+ : mSandbox(aSandbox), mCopied(false), mBuf(nullptr) {
+ if (aBuf) {
+ mBuf = rlbox::copy_memory_or_grant_access(*mSandbox, aBuf, aLen, false,
+ mCopied);
+ }
+ };
+ ~RLBoxTransferBufferToSandbox() {
+ if (mCopied) {
+ mSandbox->free_in_sandbox(mBuf);
+ }
+ };
+ rlbox::tainted<const T*, S> operator*() const { return mBuf; };
+
+ private:
+ rlbox::rlbox_sandbox<S>* mSandbox;
+ bool mCopied;
+ rlbox::tainted<const T*, S> mBuf;
+};
+
+/* The RLBoxAllocateInSandbox class is used to allocate data int sandbox that is
+ * automatically freed when the RLBoxAllocateInSandbox is out of scope. NOTE:
+ * The sandbox lifetime must outlive all of its RLBoxAllocateInSandbox'ations.
+ */
+template <typename T, typename S>
+class MOZ_STACK_CLASS RLBoxAllocateInSandbox {
+ public:
+ RLBoxAllocateInSandbox() = delete;
+ explicit RLBoxAllocateInSandbox(rlbox::rlbox_sandbox<S>* aSandbox)
+ : mSandbox(aSandbox) {
+ mPtr = mSandbox->template malloc_in_sandbox<T>();
+ };
+ ~RLBoxAllocateInSandbox() {
+ if (mPtr) {
+ mSandbox->free_in_sandbox(mPtr);
+ }
+ };
+ rlbox::tainted<T*, S> get() const { return mPtr; };
+
+ private:
+ rlbox::rlbox_sandbox<S>* mSandbox;
+ rlbox::tainted<T*, S> mPtr;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/ShutdownPhase.h b/xpcom/base/ShutdownPhase.h
new file mode 100644
index 0000000000..ebe5ebd37e
--- /dev/null
+++ b/xpcom/base/ShutdownPhase.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ShutdownPhase_h
+#define ShutdownPhase_h
+
+namespace mozilla {
+
+// Must be contiguous starting at 0
+enum class ShutdownPhase {
+ NotInShutdown = 0,
+ AppShutdownConfirmed,
+ AppShutdownNetTeardown,
+ AppShutdownTeardown,
+ AppShutdown,
+ AppShutdownQM,
+ AppShutdownTelemetry,
+ XPCOMWillShutdown,
+ XPCOMShutdown,
+ XPCOMShutdownThreads,
+ XPCOMShutdownFinal,
+ CCPostLastCycleCollection,
+ ShutdownPhase_Length, // never pass this value
+ First = AppShutdownConfirmed // for iteration
+};
+
+} // namespace mozilla
+
+#endif // ShutdownPhase_h
diff --git a/xpcom/base/SizeOfState.h b/xpcom/base/SizeOfState.h
new file mode 100644
index 0000000000..2df0b24e93
--- /dev/null
+++ b/xpcom/base/SizeOfState.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SizeOfState_h
+#define SizeOfState_h
+
+#include "mozilla/fallible.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Unused.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+// This file includes types that are useful during memory reporting, but which
+// cannot be put into mfbt/MemoryReporting.h because they depend on things that
+// are not in MFBT.
+
+namespace mozilla {
+
+// A table of seen pointers. Useful when measuring structures that contain
+// nodes that may be pointed to from multiple places, e.g. via RefPtr (in C++
+// code) or Arc (in Rust code).
+class SeenPtrs : public nsTHashtable<nsPtrHashKey<const void>> {
+ public:
+ // Returns true if we have seen this pointer before, false otherwise. Also
+ // remembers this pointer for later queries.
+ bool HaveSeenPtr(const void* aPtr) {
+ uint32_t oldCount = Count();
+
+ mozilla::Unused << PutEntry(aPtr, fallible);
+
+ // If the counts match, there are two possibilities.
+ //
+ // - Lookup succeeded: we've seen the pointer before, and didn't need to
+ // add a new entry.
+ //
+ // - PutEntry() tried to add the entry and failed due to lack of memory. In
+ // this case we can't tell if this pointer has been seen before (because
+ // the table is in an unreliable state and may have dropped previous
+ // insertions). When doing memory reporting it's better to err on the
+ // side of under-reporting rather than over-reporting, so we assume we've
+ // seen the pointer before.
+ //
+ return oldCount == Count();
+ }
+};
+
+// Memory reporting state. Some memory measuring functions
+// (SizeOfIncludingThis(), etc.) just need a MallocSizeOf parameter, but some
+// also need a record of pointers that have been seen and should not be
+// re-measured. This class encapsulates both of those things.
+class SizeOfState {
+ public:
+ explicit SizeOfState(MallocSizeOf aMallocSizeOf)
+ : mMallocSizeOf(aMallocSizeOf) {}
+
+ bool HaveSeenPtr(const void* aPtr) { return mSeenPtrs.HaveSeenPtr(aPtr); }
+
+ MallocSizeOf mMallocSizeOf;
+ SeenPtrs mSeenPtrs;
+};
+
+} // namespace mozilla
+
+#endif // SizeOfState_h
diff --git a/xpcom/base/StaticLocalPtr.h b/xpcom/base/StaticLocalPtr.h
new file mode 100644
index 0000000000..2e2fc035bd
--- /dev/null
+++ b/xpcom/base/StaticLocalPtr.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StaticLocalPtr_h
+#define mozilla_StaticLocalPtr_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+/**
+ * StaticLocalAutoPtr and StaticLocalRefPtr are like UniquePtr and RefPtr,
+ * except they are suitable for use as "magic static" local variables -- that
+ * is, they are able to take advantage of C++11's guarantee of thread safety
+ * during initialization by atomically constructing both the smart pointer
+ * itself as well as the object being pointed to.
+ *
+ * A static local instance of StaticLocal{Auto,Ref}Ptr does not cause the
+ * compiler to emit any atexit calls. In order to accomplish this,
+ * StaticLocal{Auto,Ref}Ptr must have a trivial destructor. As a consequence,
+ * it does not delete/release its raw pointer upon destruction.
+ *
+ * The clang plugin, run as part of our "static analysis" builds, makes it a
+ * compile-time error to use StaticLocal{Auto,Ref}Ptr as anything except a
+ * static local variable.
+ *
+ * StaticLocal{Auto,Ref}Ptr have a limited interface as compared to
+ * ns{Auto,Ref}Ptr; this is intentional, since their range of acceptable uses is
+ * smaller.
+ */
+
+template <typename T>
+class MOZ_STATIC_LOCAL_CLASS StaticLocalAutoPtr final {
+ public:
+ explicit StaticLocalAutoPtr(T* aRawPtr) : mRawPtr(aRawPtr) {}
+
+ StaticLocalAutoPtr(StaticLocalAutoPtr<T>&& aOther) : mRawPtr(aOther.mRawPtr) {
+ aOther.mRawPtr = nullptr;
+ }
+
+ StaticLocalAutoPtr<T>& operator=(T* aRhs) {
+ Assign(aRhs);
+ return *this;
+ }
+
+ T* get() const { return mRawPtr; }
+
+ operator T*() const { return get(); }
+
+ T* operator->() const {
+ MOZ_ASSERT(mRawPtr);
+ return get();
+ }
+
+ T& operator*() const { return *get(); }
+
+ T* forget() {
+ T* temp = mRawPtr;
+ mRawPtr = nullptr;
+ return temp;
+ }
+
+ private:
+ StaticLocalAutoPtr(const StaticLocalAutoPtr<T>& aOther) = delete;
+
+ // We do not allow assignment as the intention of this class is to only
+ // assign to mRawPtr during construction.
+ StaticLocalAutoPtr& operator=(const StaticLocalAutoPtr<T>& aOther) = delete;
+ StaticLocalAutoPtr& operator=(StaticLocalAutoPtr<T>&&) = delete;
+
+ void Assign(T* aNewPtr) {
+ MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr);
+ T* oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ delete oldPtr;
+ }
+
+ T* mRawPtr;
+};
+
+template <typename T>
+class MOZ_STATIC_LOCAL_CLASS StaticLocalRefPtr final {
+ public:
+ explicit StaticLocalRefPtr(T* aRawPtr) : mRawPtr(nullptr) {
+ AssignWithAddref(aRawPtr);
+ }
+
+ explicit StaticLocalRefPtr(already_AddRefed<T>& aPtr) : mRawPtr(nullptr) {
+ AssignAssumingAddRef(aPtr.take());
+ }
+
+ explicit StaticLocalRefPtr(already_AddRefed<T>&& aPtr) : mRawPtr(nullptr) {
+ AssignAssumingAddRef(aPtr.take());
+ }
+
+ StaticLocalRefPtr(const StaticLocalRefPtr<T>& aPtr)
+ : StaticLocalRefPtr(aPtr.mRawPtr) {}
+
+ StaticLocalRefPtr(StaticLocalRefPtr<T>&& aPtr) : mRawPtr(aPtr.mRawPtr) {
+ aPtr.mRawPtr = nullptr;
+ }
+
+ StaticLocalRefPtr<T>& operator=(T* aRhs) {
+ AssignWithAddref(aRhs);
+ return *this;
+ }
+
+ already_AddRefed<T> forget() {
+ T* temp = mRawPtr;
+ mRawPtr = nullptr;
+ return already_AddRefed<T>(temp);
+ }
+
+ T* get() const { return mRawPtr; }
+
+ operator T*() const { return get(); }
+
+ T* operator->() const {
+ MOZ_ASSERT(mRawPtr);
+ return get();
+ }
+
+ T& operator*() const { return *get(); }
+
+ private:
+ // We do not allow assignment as the intention of this class is to only
+ // assign to mRawPtr during construction.
+ StaticLocalRefPtr<T>& operator=(const StaticLocalRefPtr<T>& aRhs) = delete;
+ StaticLocalRefPtr<T>& operator=(StaticLocalRefPtr<T>&& aRhs) = delete;
+
+ void AssignWithAddref(T* aNewPtr) {
+ if (aNewPtr) {
+ aNewPtr->AddRef();
+ }
+ AssignAssumingAddRef(aNewPtr);
+ }
+
+ void AssignAssumingAddRef(T* aNewPtr) {
+ T* oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ if (oldPtr) {
+ oldPtr->Release();
+ }
+ }
+
+ T* MOZ_OWNING_REF mRawPtr;
+};
+
+namespace StaticLocalPtr_internal {
+class Zero;
+} // namespace StaticLocalPtr_internal
+
+#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \
+ template <__VA_ARGS__> \
+ inline bool operator==(type1 lhs, type2 rhs) { \
+ return eq_fn; \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator==(type2 lhs, type1 rhs) { \
+ return rhs == lhs; \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator!=(type1 lhs, type2 rhs) { \
+ return !(lhs == rhs); \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator!=(type2 lhs, type1 rhs) { \
+ return !(lhs == rhs); \
+ }
+
+// StaticLocalAutoPtr (in)equality operators
+
+template <class T, class U>
+inline bool operator==(const StaticLocalAutoPtr<T>& aLhs,
+ const StaticLocalAutoPtr<U>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class T, class U>
+inline bool operator!=(const StaticLocalAutoPtr<T>& aLhs,
+ const StaticLocalAutoPtr<U>& aRhs) {
+ return !(aLhs == aRhs);
+}
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&, const U*,
+ lhs.get() == rhs, class T, class U)
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&, U*, lhs.get() == rhs,
+ class T, class U)
+
+// Let us compare StaticLocalAutoPtr to 0.
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr<T>&,
+ StaticLocalPtr_internal::Zero*,
+ lhs.get() == nullptr, class T)
+
+// StaticLocalRefPtr (in)equality operators
+
+template <class T, class U>
+inline bool operator==(const StaticLocalRefPtr<T>& aLhs,
+ const StaticLocalRefPtr<U>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class T, class U>
+inline bool operator!=(const StaticLocalRefPtr<T>& aLhs,
+ const StaticLocalRefPtr<U>& aRhs) {
+ return !(aLhs == aRhs);
+}
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&, const U*,
+ lhs.get() == rhs, class T, class U)
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&, U*, lhs.get() == rhs,
+ class T, class U)
+
+// Let us compare StaticLocalRefPtr to 0.
+REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr<T>&,
+ StaticLocalPtr_internal::Zero*,
+ lhs.get() == nullptr, class T)
+
+#undef REFLEXIVE_EQUALITY_OPERATORS
+
+} // namespace mozilla
+
+// Declared in mozilla/RefPtr.h
+template <class T>
+template <class U>
+RefPtr<T>::RefPtr(const mozilla::StaticLocalRefPtr<U>& aOther)
+ : RefPtr(aOther.get()) {}
+
+template <class T>
+template <class U>
+RefPtr<T>& RefPtr<T>::operator=(const mozilla::StaticLocalRefPtr<U>& aOther) {
+ return operator=(aOther.get());
+}
+
+template <class T>
+inline already_AddRefed<T> do_AddRef(
+ const mozilla::StaticLocalRefPtr<T>& aObj) {
+ RefPtr<T> ref(aObj);
+ return ref.forget();
+}
+
+#endif // mozilla_StaticLocalPtr_h
diff --git a/xpcom/base/StaticMonitor.h b/xpcom/base/StaticMonitor.h
new file mode 100644
index 0000000000..b35d3871d2
--- /dev/null
+++ b/xpcom/base/StaticMonitor.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StaticMonitor_h
+#define mozilla_StaticMonitor_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/ThreadSafety.h"
+
+namespace mozilla {
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("monitor")
+ StaticMonitor {
+ public:
+ // In debug builds, check that mMutex is initialized for us as we expect by
+ // the compiler. In non-debug builds, don't declare a constructor so that
+ // the compiler can see that the constructor is trivial.
+#ifdef DEBUG
+ StaticMonitor() { MOZ_ASSERT(!mMutex); }
+#endif
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); }
+
+ void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); }
+
+ void Wait() { CondVar()->Wait(); }
+ CVStatus Wait(TimeDuration aDuration) {
+ AssertCurrentThreadOwns();
+ return CondVar()->Wait(aDuration);
+ }
+
+ void Notify() { CondVar()->Notify(); }
+ void NotifyAll() { CondVar()->NotifyAll(); }
+
+ void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) {
+#ifdef DEBUG
+ Mutex()->AssertCurrentThreadOwns();
+#endif
+ }
+
+ private:
+ OffTheBooksMutex* Mutex() {
+ if (mMutex) {
+ return mMutex;
+ }
+
+ OffTheBooksMutex* mutex = new OffTheBooksMutex("StaticMutex");
+ if (!mMutex.compareExchange(nullptr, mutex)) {
+ delete mutex;
+ }
+
+ return mMutex;
+ }
+
+ OffTheBooksCondVar* CondVar() {
+ if (mCondVar) {
+ return mCondVar;
+ }
+
+ OffTheBooksCondVar* condvar =
+ new OffTheBooksCondVar(*Mutex(), "StaticCondVar");
+ if (!mCondVar.compareExchange(nullptr, condvar)) {
+ delete condvar;
+ }
+
+ return mCondVar;
+ }
+
+ Atomic<OffTheBooksMutex*> mMutex;
+ Atomic<OffTheBooksCondVar*> mCondVar;
+
+ // Disallow copy constructor, but only in debug mode. We only define
+ // a default constructor in debug mode (see above); if we declared
+ // this constructor always, the compiler wouldn't generate a trivial
+ // default constructor for us in non-debug mode.
+#ifdef DEBUG
+ StaticMonitor(const StaticMonitor& aOther);
+#endif
+
+ // Disallow these operators.
+ StaticMonitor& operator=(const StaticMonitor& aRhs);
+ static void* operator new(size_t) noexcept(true);
+ static void operator delete(void*);
+};
+
+class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY StaticMonitorAutoLock {
+ public:
+ explicit StaticMonitorAutoLock(StaticMonitor& aMonitor)
+ MOZ_CAPABILITY_ACQUIRE(aMonitor)
+ : mMonitor(&aMonitor) {
+ mMonitor->Lock();
+ }
+
+ ~StaticMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); }
+
+ void Wait() { mMonitor->Wait(); }
+ CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); }
+
+ void Notify() { mMonitor->Notify(); }
+ void NotifyAll() { mMonitor->NotifyAll(); }
+
+ private:
+ StaticMonitorAutoLock();
+ StaticMonitorAutoLock(const StaticMonitorAutoLock&);
+ StaticMonitorAutoLock& operator=(const StaticMonitorAutoLock&);
+ static void* operator new(size_t) noexcept(true);
+
+ StaticMonitor* mMonitor;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/StaticMutex.h b/xpcom/base/StaticMutex.h
new file mode 100644
index 0000000000..08c0288486
--- /dev/null
+++ b/xpcom/base/StaticMutex.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StaticMutex_h
+#define mozilla_StaticMutex_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+
+/**
+ * StaticMutex is a Mutex that can (and in fact, must) be used as a
+ * global/static variable.
+ *
+ * The main reason to use StaticMutex as opposed to
+ * StaticAutoPtr<OffTheBooksMutex> is that we instantiate the StaticMutex in a
+ * thread-safe manner the first time it's used.
+ *
+ * The same caveats that apply to StaticAutoPtr apply to StaticMutex. In
+ * particular, do not use StaticMutex as a stack variable or a class instance
+ * variable, because this class relies on the fact that global variablies are
+ * initialized to 0 in order to initialize mMutex. It is only safe to use
+ * StaticMutex as a global or static variable.
+ */
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("mutex")
+ StaticMutex {
+ public:
+ // In debug builds, check that mMutex is initialized for us as we expect by
+ // the compiler. In non-debug builds, don't declare a constructor so that
+ // the compiler can see that the constructor is trivial.
+#ifdef DEBUG
+ StaticMutex() { MOZ_ASSERT(!mMutex); }
+#endif
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); }
+
+ void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); }
+
+ void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) {
+#ifdef DEBUG
+ Mutex()->AssertCurrentThreadOwns();
+#endif
+ }
+
+ private:
+ OffTheBooksMutex* Mutex() {
+ if (mMutex) {
+ return mMutex;
+ }
+
+ OffTheBooksMutex* mutex = new OffTheBooksMutex("StaticMutex");
+ if (!mMutex.compareExchange(nullptr, mutex)) {
+ delete mutex;
+ }
+
+ return mMutex;
+ }
+
+ Atomic<OffTheBooksMutex*, SequentiallyConsistent> mMutex;
+
+ // Disallow copy constructor, but only in debug mode. We only define
+ // a default constructor in debug mode (see above); if we declared
+ // this constructor always, the compiler wouldn't generate a trivial
+ // default constructor for us in non-debug mode.
+#ifdef DEBUG
+ StaticMutex(StaticMutex& aOther);
+#endif
+
+ // Disallow these operators.
+ StaticMutex& operator=(StaticMutex* aRhs);
+ static void* operator new(size_t) noexcept(true);
+ static void operator delete(void*);
+};
+
+typedef detail::BaseAutoLock<StaticMutex&> StaticMutexAutoLock;
+typedef detail::BaseAutoUnlock<StaticMutex&> StaticMutexAutoUnlock;
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/StaticPtr.h b/xpcom/base/StaticPtr.h
new file mode 100644
index 0000000000..38fcd82f97
--- /dev/null
+++ b/xpcom/base/StaticPtr.h
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_StaticPtr_h
+#define mozilla_StaticPtr_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+/**
+ * StaticAutoPtr and StaticRefPtr are like UniquePtr and RefPtr, except they
+ * are suitable for use as global variables.
+ *
+ * In particular, a global instance of Static{Auto,Ref}Ptr doesn't cause the
+ * compiler to emit a static initializer.
+ *
+ * Since the compiler guarantees that all global variables are initialized to
+ * 0, the default constexpr constructors will result in no actual code being
+ * generated. Since we rely on this, the clang plugin, run as part of our
+ * "static analysis" builds, makes it a compile-time error to use
+ * Static{Auto,Ref}Ptr as anything except a global variable.
+ *
+ * Static{Auto,Ref}Ptr have a limited interface as compared to ns{Auto,Ref}Ptr;
+ * this is intentional, since their range of acceptable uses is smaller.
+ */
+
+template <class T>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticAutoPtr {
+ public:
+ constexpr StaticAutoPtr() = default;
+ StaticAutoPtr(const StaticAutoPtr&) = delete;
+
+ StaticAutoPtr<T>& operator=(T* aRhs) {
+ Assign(aRhs);
+ return *this;
+ }
+
+ T* get() const { return mRawPtr; }
+
+ operator T*() const { return get(); }
+
+ T* operator->() const {
+ MOZ_ASSERT(mRawPtr);
+ return get();
+ }
+
+ T& operator*() const { return *get(); }
+
+ T* forget() {
+ T* temp = mRawPtr;
+ mRawPtr = nullptr;
+ return temp;
+ }
+
+ private:
+ void Assign(T* aNewPtr) {
+ MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr);
+ T* oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ delete oldPtr;
+ }
+
+ T* mRawPtr = nullptr;
+};
+
+template <class T>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr {
+ public:
+ constexpr StaticRefPtr() = default;
+ StaticRefPtr(const StaticRefPtr&) = delete;
+
+ StaticRefPtr<T>& operator=(T* aRhs) {
+ AssignWithAddref(aRhs);
+ return *this;
+ }
+
+ StaticRefPtr<T>& operator=(const StaticRefPtr<T>& aRhs) {
+ return (this = aRhs.mRawPtr);
+ }
+
+ StaticRefPtr<T>& operator=(already_AddRefed<T>& aRhs) {
+ AssignAssumingAddRef(aRhs.take());
+ return *this;
+ }
+
+ template <typename U>
+ StaticRefPtr<T>& operator=(RefPtr<U>&& aRhs) {
+ AssignAssumingAddRef(aRhs.forget().take());
+ return *this;
+ }
+
+ StaticRefPtr<T>& operator=(already_AddRefed<T>&& aRhs) {
+ AssignAssumingAddRef(aRhs.take());
+ return *this;
+ }
+
+ already_AddRefed<T> forget() {
+ T* temp = mRawPtr;
+ mRawPtr = nullptr;
+ return already_AddRefed<T>(temp);
+ }
+
+ T* get() const { return mRawPtr; }
+
+ operator T*() const { return get(); }
+
+ T* operator->() const {
+ MOZ_ASSERT(mRawPtr);
+ return get();
+ }
+
+ T& operator*() const { return *get(); }
+
+ private:
+ void AssignWithAddref(T* aNewPtr) {
+ if (aNewPtr) {
+ RefPtrTraits<T>::AddRef(aNewPtr);
+ }
+ AssignAssumingAddRef(aNewPtr);
+ }
+
+ void AssignAssumingAddRef(T* aNewPtr) {
+ T* oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ if (oldPtr) {
+ RefPtrTraits<T>::Release(oldPtr);
+ }
+ }
+
+ T* MOZ_OWNING_REF mRawPtr = nullptr;
+};
+
+namespace StaticPtr_internal {
+class Zero;
+} // namespace StaticPtr_internal
+
+#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \
+ template <__VA_ARGS__> \
+ inline bool operator==(type1 lhs, type2 rhs) { \
+ return eq_fn; \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator==(type2 lhs, type1 rhs) { \
+ return rhs == lhs; \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator!=(type1 lhs, type2 rhs) { \
+ return !(lhs == rhs); \
+ } \
+ \
+ template <__VA_ARGS__> \
+ inline bool operator!=(type2 lhs, type1 rhs) { \
+ return !(lhs == rhs); \
+ }
+
+// StaticAutoPtr (in)equality operators
+
+template <class T, class U>
+inline bool operator==(const StaticAutoPtr<T>& aLhs,
+ const StaticAutoPtr<U>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class T, class U>
+inline bool operator!=(const StaticAutoPtr<T>& aLhs,
+ const StaticAutoPtr<U>& aRhs) {
+ return !(aLhs == aRhs);
+}
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, const U*,
+ lhs.get() == rhs, class T, class U)
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, U*, lhs.get() == rhs,
+ class T, class U)
+
+// Let us compare StaticAutoPtr to 0.
+REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr<T>&, StaticPtr_internal::Zero*,
+ lhs.get() == nullptr, class T)
+
+// StaticRefPtr (in)equality operators
+
+template <class T, class U>
+inline bool operator==(const StaticRefPtr<T>& aLhs,
+ const StaticRefPtr<U>& aRhs) {
+ return aLhs.get() == aRhs.get();
+}
+
+template <class T, class U>
+inline bool operator!=(const StaticRefPtr<T>& aLhs,
+ const StaticRefPtr<U>& aRhs) {
+ return !(aLhs == aRhs);
+}
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, const U*, lhs.get() == rhs,
+ class T, class U)
+
+REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, U*, lhs.get() == rhs,
+ class T, class U)
+
+// Let us compare StaticRefPtr to 0.
+REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr<T>&, StaticPtr_internal::Zero*,
+ lhs.get() == nullptr, class T)
+
+#undef REFLEXIVE_EQUALITY_OPERATORS
+
+} // namespace mozilla
+
+// Declared in mozilla/RefPtr.h
+template <class T>
+template <class U>
+RefPtr<T>::RefPtr(const mozilla::StaticRefPtr<U>& aOther)
+ : RefPtr(aOther.get()) {}
+
+template <class T>
+template <class U>
+RefPtr<T>& RefPtr<T>::operator=(const mozilla::StaticRefPtr<U>& aOther) {
+ return operator=(aOther.get());
+}
+
+template <class T>
+inline already_AddRefed<T> do_AddRef(const mozilla::StaticRefPtr<T>& aObj) {
+ RefPtr<T> ref(aObj);
+ return ref.forget();
+}
+
+#endif
diff --git a/xpcom/base/components.conf b/xpcom/base/components.conf
new file mode 100644
index 0000000000..4a22da67cf
--- /dev/null
+++ b/xpcom/base/components.conf
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{cb6cdb94-e417-4601-b4a5-f991bf41453d}',
+ 'contract_ids': ['@mozilla.org/xpcom/debug;1'],
+ 'legacy_constructor': 'nsDebugImpl::Create',
+ 'headers': ['nsDebugImpl.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS,
+ },
+ {
+ 'cid': '{68bf4793-5204-45cf-9ee2-69adffbc2e38}',
+ 'contract_ids': ['@mozilla.org/xpcom/memory-watcher;1'],
+ 'singleton': True,
+ 'type': 'mozilla::nsAvailableMemoryWatcherBase',
+ 'headers': ['/xpcom/base/AvailableMemoryWatcher.h'],
+ 'constructor': 'mozilla::nsAvailableMemoryWatcherBase::GetSingleton',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build
new file mode 100644
index 0000000000..1ac409ed04
--- /dev/null
+++ b/xpcom/base/moz.build
@@ -0,0 +1,271 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIAvailableMemoryWatcherBase.idl",
+ "nsIConsoleListener.idl",
+ "nsIConsoleMessage.idl",
+ "nsIConsoleService.idl",
+ "nsICycleCollectorListener.idl",
+ "nsIDebug2.idl",
+ "nsIException.idl",
+ "nsIInterfaceRequestor.idl",
+ "nsIMemoryInfoDumper.idl",
+ "nsIMemoryReporter.idl",
+ "nsISecurityConsoleMessage.idl",
+ "nsISupports.idl",
+ "nsIUUIDGenerator.idl",
+ "nsIVersionComparator.idl",
+ "nsIWeakReference.idl",
+ "nsrootidl.idl",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ XPIDL_SOURCES += [
+ "nsIMacPreferencesReader.idl",
+ ]
+ EXPORTS += [
+ "nsObjCExceptions.h",
+ ]
+ EXPORTS.mozilla += [
+ "MacHelpers.h",
+ "MacStringHelpers.h",
+ "nsMacPreferencesReader.h",
+ ]
+ UNIFIED_SOURCES += [
+ "MacHelpers.mm",
+ "MacStringHelpers.mm",
+ "nsMacPreferencesReader.mm",
+ "nsObjCExceptions.mm",
+ ]
+
+XPIDL_MODULE = "xpcom_base"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "!ErrorList.h",
+ "!ErrorNamesInternal.h",
+ "CodeAddressService.h",
+ "nsAlgorithm.h",
+ "nsAutoRef.h",
+ "nsCom.h",
+ "nsCOMPtr.h",
+ "nscore.h",
+ "nsCRTGlue.h",
+ "nsCycleCollectionNoteChild.h",
+ "nsCycleCollectionNoteRootCallback.h",
+ "nsCycleCollectionParticipant.h",
+ "nsCycleCollectionTraversalCallback.h",
+ "nsCycleCollector.h",
+ "nsDebug.h",
+ "nsDebugImpl.h",
+ "nsDumpUtils.h",
+ "nsError.h",
+ "nsGZFileWriter.h",
+ "nsIClassInfoImpl.h",
+ "nsID.h",
+ "nsIDUtils.h",
+ "nsIInterfaceRequestorUtils.h",
+ "nsINIParser.h",
+ "nsInterfaceRequestorAgg.h",
+ "nsISizeOf.h",
+ "nsISupportsImpl.h",
+ "nsISupportsUtils.h",
+ "nsIWeakReferenceUtils.h",
+ "nsMaybeWeakPtr.h",
+ "nsMemory.h",
+ "nsMemoryReporterManager.h",
+ "nsQueryObject.h",
+ "nsSystemInfo.h",
+ "nsTraceRefcnt.h",
+ "nsVersionComparator.h",
+ "nsWeakReference.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS += [
+ "nsWindowsHelpers.h",
+ ]
+ if CONFIG["CC_TYPE"] not in ("gcc", "clang"):
+ OS_LIBS += [
+ "wscapi",
+ ]
+
+EXPORTS.mozilla += [
+ "AppShutdown.h",
+ "AutoRestore.h",
+ "AvailableMemoryTracker.h",
+ "AvailableMemoryWatcher.h",
+ "ClearOnShutdown.h",
+ "CountingAllocatorBase.h",
+ "CycleCollectedJSContext.h",
+ "CycleCollectedJSRuntime.h",
+ "Debug.h",
+ "DebuggerOnGCRunnable.h",
+ "DeferredFinalize.h",
+ "EnumeratedArrayCycleCollection.h",
+ "ErrorNames.h",
+ "GkRustUtils.h",
+ "HoldDropJSObjects.h",
+ "IntentionalCrash.h",
+ "JSObjectHolder.h",
+ "JSONStringWriteFuncs.h",
+ "Logging.h",
+ "MemoryInfo.h",
+ "MemoryMapping.h",
+ "MemoryReportingProcess.h",
+ "MemoryTelemetry.h",
+ "nsMemoryInfoDumper.h",
+ "NSPRLogModulesParser.h",
+ "OwningNonNull.h",
+ "RLBoxSandboxPool.h",
+ "RLBoxUtils.h",
+ "ShutdownPhase.h",
+ "SizeOfState.h",
+ "StaticLocalPtr.h",
+ "StaticMonitor.h",
+ "StaticMutex.h",
+ "StaticPtr.h",
+]
+
+SOURCES += [
+ # nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't
+ # optimized away oddly.
+ "nsDebugImpl.cpp",
+ # nsDumpUtils.cpp includes SpecialSystemDirectory.h which includes
+ # nsLocalFileMac.h which upsets other files in this dir that have a different
+ # idea about what `TextRange` means.
+ "nsDumpUtils.cpp",
+]
+SOURCES["nsDebugImpl.cpp"].no_pgo = True
+
+UNIFIED_SOURCES += [
+ "AppShutdown.cpp",
+ "AvailableMemoryTracker.cpp",
+ "AvailableMemoryWatcher.cpp",
+ "ClearOnShutdown.cpp",
+ "CycleCollectedJSContext.cpp",
+ "CycleCollectedJSRuntime.cpp",
+ "Debug.cpp",
+ "DebuggerOnGCRunnable.cpp",
+ "DeferredFinalize.cpp",
+ "ErrorNames.cpp",
+ "GkRustUtils.cpp",
+ "HoldDropJSObjects.cpp",
+ "JSObjectHolder.cpp",
+ "LogCommandLineHandler.cpp",
+ "Logging.cpp",
+ "LogModulePrefWatcher.cpp",
+ "MemoryTelemetry.cpp",
+ "nsClassInfoImpl.cpp",
+ "nsCOMPtr.cpp",
+ "nsConsoleMessage.cpp",
+ "nsConsoleService.cpp",
+ "nsCRTGlue.cpp",
+ "nsCycleCollectionParticipant.cpp",
+ "nsCycleCollector.cpp",
+ "nsCycleCollectorTraceJSHelpers.cpp",
+ "nsGZFileWriter.cpp",
+ "nsID.cpp",
+ "nsIInterfaceRequestorUtils.cpp",
+ "nsINIParser.cpp",
+ "nsInterfaceRequestorAgg.cpp",
+ "nsISupportsImpl.cpp",
+ "nsMemoryImpl.cpp",
+ "nsMemoryInfoDumper.cpp",
+ "nsMemoryReporterManager.cpp",
+ "NSPRLogModulesParser.cpp",
+ "nsSecurityConsoleMessage.cpp",
+ "nsSystemInfo.cpp",
+ "nsTraceRefcnt.cpp",
+ "nsUUIDGenerator.cpp",
+ "nsVersionComparator.cpp",
+ "nsVersionComparatorImpl.cpp",
+ "nsWeakReference.cpp",
+ "RLBoxSandboxPool.cpp",
+]
+
+if CONFIG["OS_TARGET"] in ("Linux", "Android"):
+ UNIFIED_SOURCES += [
+ "MemoryMapping.cpp",
+ ]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "AvailableMemoryWatcherWin.cpp",
+ "MemoryInfo.cpp",
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+ UNIFIED_SOURCES += [
+ "AvailableMemoryWatcherMac.cpp",
+ ]
+ EXPORTS.mozilla += [
+ "MemoryPressureLevelMac.h",
+ ]
+
+if CONFIG["OS_TARGET"] == "Linux":
+ UNIFIED_SOURCES += [
+ "AvailableMemoryWatcherLinux.cpp",
+ ]
+ EXPORTS.mozilla += [
+ "AvailableMemoryWatcherUtils.h",
+ ]
+
+if CONFIG["MOZ_PHC"]:
+ DEFINES["MOZ_PHC"] = True
+
+GeneratedFile("ErrorList.h", script="ErrorList.py", entry_point="error_list_h")
+GeneratedFile(
+ "ErrorNamesInternal.h", script="ErrorList.py", entry_point="error_names_internal_h"
+)
+GeneratedFile("error_list.rs", script="ErrorList.py", entry_point="error_list_rs")
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsMacUtilsImpl.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "nsCrashOnException.cpp",
+ ]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ EXPORTS.mozilla += [
+ "!gk_rust_utils_ffi_generated.h",
+ ]
+
+ CbindgenHeader("gk_rust_utils_ffi_generated.h", inputs=["/xpcom/rust/gkrust_utils"])
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "../build",
+ "/dom/base",
+ "/mfbt",
+ "/netwerk/base",
+ "/xpcom/ds",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+if CONFIG["MOZ_PHC"]:
+ EXPORTS.mozilla += [
+ "PHCManager.h",
+ ]
+
+ DEFINES["MOZ_PHC"] = 1
+
+ UNIFIED_SOURCES += ["PHCManager.cpp"]
+
+with Files("PHCManager.*"):
+ BUG_COMPONENT = ("Core", "Memory Allocator")
diff --git a/xpcom/base/nsAlgorithm.h b/xpcom/base/nsAlgorithm.h
new file mode 100644
index 0000000000..1c4bb0e9dc
--- /dev/null
+++ b/xpcom/base/nsAlgorithm.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAlgorithm_h___
+#define nsAlgorithm_h___
+
+#include <cstdint>
+#include "mozilla/Assertions.h"
+
+template <class T>
+inline T NS_ROUNDUP(const T& aA, const T& aB) {
+ return ((aA + (aB - 1)) / aB) * aB;
+}
+
+// We use these instead of std::min/max because we can't include the algorithm
+// header in all of XPCOM because the stl wrappers will error out when included
+// in parts of XPCOM. These functions should never be used outside of XPCOM.
+template <class T>
+inline const T& XPCOM_MIN(const T& aA, const T& aB) {
+ return aB < aA ? aB : aA;
+}
+
+// Must return b when a == b in case a is -0
+template <class T>
+inline const T& XPCOM_MAX(const T& aA, const T& aB) {
+ return aA > aB ? aA : aB;
+}
+
+namespace mozilla {
+
+template <class T>
+inline const T& clamped(const T& aA, const T& aMin, const T& aMax) {
+ MOZ_ASSERT(aMax >= aMin,
+ "clamped(): aMax must be greater than or equal to aMin");
+ return XPCOM_MIN(XPCOM_MAX(aA, aMin), aMax);
+}
+
+} // namespace mozilla
+
+template <class InputIterator, class T>
+inline uint32_t NS_COUNT(InputIterator& aFirst, const InputIterator& aLast,
+ const T& aValue) {
+ uint32_t result = 0;
+ for (; aFirst != aLast; ++aFirst)
+ if (*aFirst == aValue) {
+ ++result;
+ }
+ return result;
+}
+
+#endif // !defined(nsAlgorithm_h___)
diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h
new file mode 100644
index 0000000000..9a1f3ddd93
--- /dev/null
+++ b/xpcom/base/nsAutoRef.h
@@ -0,0 +1,489 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// NB: This code may be used from non-XPCOM code, in particular, the
+// Windows Default Browser Agent.
+
+#ifndef nsAutoRef_h_
+#define nsAutoRef_h_
+
+#include "mozilla/Attributes.h"
+
+template <class T>
+class nsSimpleRef;
+template <class T>
+class nsAutoRefBase;
+template <class T>
+class nsReturnRef;
+template <class T>
+class nsReturningRef;
+
+/**
+ * template <class T> class nsAutoRef
+ *
+ * A class that holds a handle to a resource that must be released.
+ * No reference is added on construction.
+ *
+ * No copy constructor nor copy assignment operators are available, so the
+ * resource will be held until released on destruction or explicitly
+ * |reset()| or transferred through provided methods.
+ *
+ * The publicly available methods are the public methods on this class and its
+ * public base classes |nsAutoRefBase<T>| and |nsSimpleRef<T>|.
+ *
+ * For function return values see |nsReturnRef<T>|.
+ *
+ * For each class |T|, |nsAutoRefTraits<T>| or |nsSimpleRef<T>| must be
+ * specialized to use |nsAutoRef<T>|.
+ *
+ * @param T A class identifying the type of reference held by the
+ * |nsAutoRef<T>| and the unique set methods for managing references
+ * to the resource (defined by |nsAutoRefTraits<T>| or
+ * |nsSimpleRef<T>|).
+ *
+ * Often this is the class representing the resource. Sometimes a
+ * new possibly-incomplete class may need to be declared.
+ *
+ *
+ * Example: An Automatically closing file descriptor
+ *
+ * // References that are simple integral types (as file-descriptors are)
+ * // usually need a new class to represent the resource and how to handle its
+ * // references.
+ * class nsRawFD;
+ *
+ * // Specializing nsAutoRefTraits<nsRawFD> describes how to manage file
+ * // descriptors, so that nsAutoRef<nsRawFD> provides automatic closing of
+ * // its file descriptor on destruction.
+ * template <>
+ * class nsAutoRefTraits<nsRawFD> {
+ * public:
+ * // The file descriptor is held in an int.
+ * typedef int RawRef;
+ * // -1 means that there is no file associated with the handle.
+ * static int Void() { return -1; }
+ * // The file associated with a file descriptor is released with close().
+ * static void Release(RawRef aFD) { close(aFD); }
+ * };
+ *
+ * // A function returning a file descriptor that must be closed.
+ * nsReturnRef<nsRawFD> get_file(const char *filename) {
+ * // Constructing from a raw file descriptor assumes ownership.
+ * nsAutoRef<nsRawFD> fd(open(filename, O_RDONLY));
+ * fcntl(fd, F_SETFD, FD_CLOEXEC);
+ * return fd.out();
+ * }
+ *
+ * void f() {
+ * unsigned char buf[1024];
+ *
+ * // Hold a file descriptor for /etc/hosts in fd1.
+ * nsAutoRef<nsRawFD> fd1(get_file("/etc/hosts"));
+ *
+ * nsAutoRef<nsRawFD> fd2;
+ * fd2.steal(fd1); // fd2 takes the file descriptor from fd1
+ * ssize_t count = read(fd1, buf, 1024); // error fd1 has no file
+ * count = read(fd2, buf, 1024); // reads from /etc/hosts
+ *
+ * // If the file descriptor is not stored then it is closed.
+ * get_file("/etc/login.defs"); // login.defs is closed
+ *
+ * // Now use fd1 to hold a file descriptor for /etc/passwd.
+ * fd1 = get_file("/etc/passwd");
+ *
+ * // The nsAutoRef<nsRawFD> can give up the file descriptor if explicitly
+ * // instructed, but the caller must then ensure that the file is closed.
+ * int rawfd = fd1.disown();
+ *
+ * // Assume ownership of another file descriptor.
+ * fd1.own(open("/proc/1/maps");
+ *
+ * // On destruction, fd1 closes /proc/1/maps and fd2 closes /etc/hosts,
+ * // but /etc/passwd is not closed.
+ * }
+ *
+ */
+
+template <class T>
+class nsAutoRef : public nsAutoRefBase<T> {
+ protected:
+ typedef nsAutoRef<T> ThisClass;
+ typedef nsAutoRefBase<T> BaseClass;
+ typedef nsSimpleRef<T> SimpleRef;
+ typedef typename BaseClass::RawRefOnly RawRefOnly;
+ typedef typename BaseClass::LocalSimpleRef LocalSimpleRef;
+
+ public:
+ nsAutoRef() = default;
+
+ // Explicit construction is required so as not to risk unintentionally
+ // releasing the resource associated with a raw ref.
+ explicit nsAutoRef(RawRefOnly aRefToRelease) : BaseClass(aRefToRelease) {}
+
+ // Construction from a nsReturnRef<T> function return value, which expects
+ // to give up ownership, transfers ownership.
+ // (nsReturnRef<T> is converted to const nsReturningRef<T>.)
+ explicit nsAutoRef(const nsReturningRef<T>& aReturning)
+ : BaseClass(aReturning) {}
+
+ // The only assignment operator provided is for transferring from an
+ // nsReturnRef smart reference, which expects to pass its ownership to
+ // another object.
+ //
+ // With raw references and other smart references, the type of the lhs and
+ // its taking and releasing nature is often not obvious from an assignment
+ // statement. Assignment from a raw ptr especially is not normally
+ // expected to release the reference.
+ //
+ // Use |steal| for taking ownership from other smart refs.
+ //
+ // For raw references, use |own| to indicate intention to have the
+ // resource released.
+
+ ThisClass& operator=(const nsReturningRef<T>& aReturning) {
+ BaseClass::steal(aReturning.mReturnRef);
+ return *this;
+ }
+
+ // Conversion to a raw reference allow the nsAutoRef<T> to often be used
+ // like a raw reference.
+ operator typename SimpleRef::RawRef() const { return this->get(); }
+
+ explicit operator bool() const { return this->HaveResource(); }
+
+ // Transfer ownership from another smart reference.
+ void steal(ThisClass& aOtherRef) { BaseClass::steal(aOtherRef); }
+
+ // Assume ownership of a raw ref.
+ //
+ // |own| has similar function to |steal|, and is useful for receiving
+ // ownership from a return value of a function. It is named differently
+ // because |own| requires more care to ensure that the function intends to
+ // give away ownership, and so that |steal| can be safely used, knowing
+ // that it won't steal ownership from any methods returning raw ptrs to
+ // data owned by a foreign object.
+ void own(RawRefOnly aRefToRelease) { BaseClass::own(aRefToRelease); }
+
+ // Exchange ownership with |aOther|
+ void swap(ThisClass& aOther) {
+ LocalSimpleRef temp;
+ temp.SimpleRef::operator=(*this);
+ SimpleRef::operator=(aOther);
+ aOther.SimpleRef::operator=(temp);
+ }
+
+ // Release the reference now.
+ void reset() {
+ this->SafeRelease();
+ LocalSimpleRef empty;
+ SimpleRef::operator=(empty);
+ }
+
+ // Pass out the reference for a function return values.
+ nsReturnRef<T> out() { return nsReturnRef<T>(this->disown()); }
+
+ // operator->() and disown() are provided by nsAutoRefBase<T>.
+ // The default nsSimpleRef<T> provides get().
+
+ // No copy constructor
+ explicit nsAutoRef(const ThisClass& aRefToSteal) = delete;
+};
+
+/**
+ * template <class T> class nsReturnRef
+ *
+ * A type for function return values that hold a reference to a resource that
+ * must be released. See also |nsAutoRef<T>::out()|.
+ */
+
+template <class T>
+class nsReturnRef : public nsAutoRefBase<T> {
+ protected:
+ typedef nsAutoRefBase<T> BaseClass;
+ typedef typename BaseClass::RawRefOnly RawRefOnly;
+
+ public:
+ // For constructing a return value with no resource
+ nsReturnRef() = default;
+
+ // For returning a smart reference from a raw reference that must be
+ // released. Explicit construction is required so as not to risk
+ // unintentionally releasing the resource associated with a raw ref.
+ MOZ_IMPLICIT nsReturnRef(RawRefOnly aRefToRelease)
+ : BaseClass(aRefToRelease) {}
+
+ // Move construction transfers ownership
+ nsReturnRef(nsReturnRef<T>&& aRefToSteal) = default;
+
+ MOZ_IMPLICIT nsReturnRef(const nsReturningRef<T>& aReturning)
+ : BaseClass(aReturning) {}
+
+ // Conversion to a temporary (const) object referring to this object so
+ // that the reference may be passed from a function return value
+ // (temporary) to another smart reference. There is no need to use this
+ // explicitly. Simply assign a nsReturnRef<T> function return value to a
+ // smart reference.
+ operator nsReturningRef<T>() { return nsReturningRef<T>(*this); }
+
+ // No conversion to RawRef operator is provided on nsReturnRef, to ensure
+ // that the return value is not carelessly assigned to a raw ptr (and the
+ // resource then released). If passing to a function that takes a raw
+ // ptr, use get or disown as appropriate.
+};
+
+/**
+ * template <class T> class nsReturningRef
+ *
+ * A class to allow ownership to be transferred from nsReturnRef function
+ * return values.
+ *
+ * It should not be necessary for clients to reference this
+ * class directly. Simply pass an nsReturnRef<T> to a parameter taking an
+ * |nsReturningRef<T>|.
+ *
+ * The conversion operator on nsReturnRef constructs a temporary wrapper of
+ * class nsReturningRef<T> around a non-const reference to the nsReturnRef.
+ * The wrapper can then be passed as an rvalue parameter.
+ */
+
+template <class T>
+class nsReturningRef {
+ private:
+ friend class nsReturnRef<T>;
+
+ explicit nsReturningRef(nsReturnRef<T>& aReturnRef)
+ : mReturnRef(aReturnRef) {}
+
+ public:
+ nsReturnRef<T>& mReturnRef;
+};
+
+/**
+ * template <class T> class nsAutoRefTraits
+ *
+ * A class describing traits of references managed by the default
+ * |nsSimpleRef<T>| implementation and thus |nsAutoRef<T>|.
+ * The default |nsSimpleRef<T> is suitable for resources with handles that
+ * have a void value. (If there is no such void value for a handle,
+ * specialize |nsSimpleRef<T>|.)
+ *
+ * Specializations must be provided for each class |T| according to the
+ * following pattern:
+ *
+ * // The template parameter |T| should be a class such that the set of fields
+ * // in class nsAutoRefTraits<T> is unique for class |T|. Usually the
+ * // resource object class is sufficient. For handles that are simple
+ * // integral typedefs, a new unique possibly-incomplete class may need to be
+ * // declared.
+ *
+ * template <>
+ * class nsAutoRefTraits<T>
+ * {
+ * // Specializations must provide a typedef for RawRef, describing the
+ * // type of the handle to the resource.
+ * typedef <handle-type> RawRef;
+ *
+ * // Specializations should define Void(), a function returning a value
+ * // suitable for a handle that does not have an associated resource.
+ * //
+ * // The return type must be a suitable as the parameter to a RawRef
+ * // constructor and operator==.
+ * //
+ * // If this method is not accessible then some limited nsAutoRef
+ * // functionality will still be available, but the default constructor,
+ * // |reset|, and most transfer of ownership methods will not be available.
+ * static <return-type> Void();
+ *
+ * // Specializations must define Release() to properly finalize the
+ * // handle to a non-void custom-deleted or reference-counted resource.
+ * static void Release(RawRef aRawRef);
+ * };
+ *
+ * See nsPointerRefTraits for example specializations for simple pointer
+ * references. See nsAutoRef for an example specialization for a non-pointer
+ * reference.
+ */
+
+template <class T>
+class nsAutoRefTraits;
+
+/**
+ * template <class T> class nsPointerRefTraits
+ *
+ * A convenience class useful as a base class for specializations of
+ * |nsAutoRefTraits<T>| where the handle to the resource is a pointer to |T|.
+ * By inheriting from this class, definitions of only Release(RawRef) and
+ * possibly AddRef(RawRef) need to be added.
+ *
+ * Examples of use:
+ *
+ * template <>
+ * class nsAutoRefTraits<PRFileDesc> : public nsPointerRefTraits<PRFileDesc>
+ * {
+ * public:
+ * static void Release(PRFileDesc *ptr) { PR_Close(ptr); }
+ * };
+ *
+ * template <>
+ * class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern>
+ * {
+ * public:
+ * static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); }
+ * static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); }
+ * };
+ */
+
+template <class T>
+class nsPointerRefTraits {
+ public:
+ // The handle is a pointer to T.
+ typedef T* RawRef;
+ // A nullptr does not have a resource.
+ static RawRef Void() { return nullptr; }
+};
+
+/**
+ * template <class T> class nsSimpleRef
+ *
+ * Constructs a non-smart reference, and provides methods to test whether
+ * there is an associated resource and (if so) get its raw handle.
+ *
+ * A default implementation is suitable for resources with handles that have a
+ * void value. This is not intended for direct use but used by |nsAutoRef<T>|.
+ *
+ * Specialize this class if there is no particular void value for the resource
+ * handle. A specialized implementation must also provide Release(RawRef),
+ */
+
+template <class T>
+class nsSimpleRef : protected nsAutoRefTraits<T> {
+ protected:
+ // The default implementation uses nsAutoRefTrait<T>.
+ // Specializations need not define this typedef.
+ typedef nsAutoRefTraits<T> Traits;
+ // The type of the handle to the resource.
+ // A specialization must provide a typedef for RawRef.
+ typedef typename Traits::RawRef RawRef;
+
+ // Construct with no resource.
+ //
+ // If this constructor is not accessible then some limited nsAutoRef
+ // functionality will still be available, but the default constructor,
+ // |reset|, and most transfer of ownership methods will not be available.
+ nsSimpleRef() : mRawRef(Traits::Void()) {}
+ // Construct with a handle to a resource.
+ // A specialization must provide this.
+ explicit nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) {}
+
+ // Test whether there is an associated resource. A specialization must
+ // provide this. The function is permitted to always return true if the
+ // default constructor is not accessible, or if Release (and AddRef) can
+ // deal with void handles.
+ bool HaveResource() const { return mRawRef != Traits::Void(); }
+
+ public:
+ // A specialization must provide get() or loose some functionality. This
+ // is inherited by derived classes and the specialization may choose
+ // whether it is public or protected.
+ RawRef get() const { return mRawRef; }
+
+ private:
+ RawRef mRawRef;
+};
+
+/**
+ * template <class T> class nsAutoRefBase
+ *
+ * Internal base class for |nsAutoRef<T>| and |nsReturnRef<T>|.
+ * Adds release on destruction to a |nsSimpleRef<T>|.
+ */
+
+template <class T>
+class nsAutoRefBase : public nsSimpleRef<T> {
+ protected:
+ typedef nsAutoRefBase<T> ThisClass;
+ typedef nsSimpleRef<T> SimpleRef;
+ typedef typename SimpleRef::RawRef RawRef;
+
+ nsAutoRefBase() = default;
+
+ // A type for parameters that should be passed a raw ref but should not
+ // accept implicit conversions (from another smart ref). (The only
+ // conversion to this type is from a raw ref so only raw refs will be
+ // accepted.)
+ class RawRefOnly {
+ public:
+ MOZ_IMPLICIT RawRefOnly(RawRef aRawRef) : mRawRef(aRawRef) {}
+ operator RawRef() const { return mRawRef; }
+
+ private:
+ RawRef mRawRef;
+ };
+
+ // Construction from a raw ref assumes ownership
+ explicit nsAutoRefBase(RawRefOnly aRefToRelease) : SimpleRef(aRefToRelease) {}
+
+ // Constructors that steal ownership
+ nsAutoRefBase(ThisClass&& aRefToSteal) : SimpleRef(aRefToSteal.disown()) {}
+ explicit nsAutoRefBase(const nsReturningRef<T>& aReturning)
+ : SimpleRef(aReturning.mReturnRef.disown()) {}
+
+ ~nsAutoRefBase() { SafeRelease(); }
+
+ // An internal class providing access to protected nsSimpleRef<T>
+ // constructors for construction of temporary simple references (that are
+ // not ThisClass).
+ class LocalSimpleRef : public SimpleRef {
+ public:
+ LocalSimpleRef() = default;
+ explicit LocalSimpleRef(RawRef aRawRef) : SimpleRef(aRawRef) {}
+ };
+
+ public:
+ ThisClass& operator=(const ThisClass& aSmartRef) = delete;
+
+ RawRef operator->() const { return this->get(); }
+
+ // Transfer ownership to a raw reference.
+ //
+ // THE CALLER MUST ENSURE THAT THE REFERENCE IS EXPLICITLY RELEASED.
+ //
+ // Is this really what you want to use? Using this removes any guarantee
+ // of release. Use nsAutoRef<T>::out() for return values, or an
+ // nsAutoRef<T> modifiable lvalue for an out parameter. Use disown() when
+ // the reference must be stored in a POD type object, such as may be
+ // preferred for a namespace-scope object with static storage duration,
+ // for example.
+ RawRef disown() {
+ RawRef temp = this->get();
+ LocalSimpleRef empty;
+ SimpleRef::operator=(empty);
+ return temp;
+ }
+
+ protected:
+ // steal and own are protected because they make no sense on nsReturnRef,
+ // but steal is implemented on this class for access to aOtherRef.disown()
+ // when aOtherRef is an nsReturnRef;
+
+ // Transfer ownership from another smart reference.
+ void steal(ThisClass& aOtherRef) { own(aOtherRef.disown()); }
+ // Assume ownership of a raw ref.
+ void own(RawRefOnly aRefToRelease) {
+ SafeRelease();
+ LocalSimpleRef ref(aRefToRelease);
+ SimpleRef::operator=(ref);
+ }
+
+ // Release a resource if there is one.
+ void SafeRelease() {
+ if (this->HaveResource()) {
+ this->Release(this->get());
+ }
+ }
+};
+
+#endif // !defined(nsAutoRef_h_)
diff --git a/xpcom/base/nsCOMPtr.cpp b/xpcom/base/nsCOMPtr.cpp
new file mode 100644
index 0000000000..ffe0cb1acd
--- /dev/null
+++ b/xpcom/base/nsCOMPtr.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+
+nsresult nsQueryInterfaceISupports::operator()(const nsIID& aIID,
+ void** aAnswer) const {
+ nsresult status;
+ if (mRawPtr) {
+ status = mRawPtr->QueryInterface(aIID, aAnswer);
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ return status;
+}
+
+nsresult nsQueryInterfaceISupportsWithError::operator()(const nsIID& aIID,
+ void** aAnswer) const {
+ nsresult status;
+ if (mRawPtr) {
+ status = mRawPtr->QueryInterface(aIID, aAnswer);
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ if (mErrorPtr) {
+ *mErrorPtr = status;
+ }
+ return status;
+}
diff --git a/xpcom/base/nsCOMPtr.h b/xpcom/base/nsCOMPtr.h
new file mode 100644
index 0000000000..d45209d5aa
--- /dev/null
+++ b/xpcom/base/nsCOMPtr.h
@@ -0,0 +1,1174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCOMPtr_h___
+#define nsCOMPtr_h___
+
+/*
+ * Having problems?
+ *
+ * See the documentation at:
+ * https://firefox-source-docs.mozilla.org/xpcom/refptr.html
+ *
+ *
+ * nsCOMPtr
+ * better than a raw pointer
+ * for owning objects
+ * -- scc
+ */
+
+#include <type_traits>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsDebug.h" // for |NS_ASSERTION|
+#include "nsISupportsUtils.h" // for |nsresult|, |NS_ADDREF|, |NS_GET_TEMPLATE_IID| et al
+
+/*
+ * WARNING: This file defines several macros for internal use only. These
+ * macros begin with the prefix |NSCAP_|. Do not use these macros in your own
+ * code. They are for internal use only for cross-platform compatibility, and
+ * are subject to change without notice.
+ */
+
+#ifdef _MSC_VER
+// Under VC++, we win by inlining StartAssignment.
+# define NSCAP_FEATURE_INLINE_STARTASSIGNMENT
+
+// Also under VC++, at the highest warning level, we are overwhelmed with
+// warnings about (unused) inline functions being removed. This is to be
+// expected with templates, so we disable the warning.
+# pragma warning(disable : 4514)
+#endif
+
+#ifdef DEBUG
+# define NSCAP_FEATURE_TEST_DONTQUERY_CASES
+#endif
+
+#ifdef __GNUC__
+// Our use of nsCOMPtr_base::mRawPtr violates the C++ standard's aliasing
+// rules. Mark it with the may_alias attribute so that gcc 3.3 and higher
+// don't reorder instructions based on aliasing assumptions for
+// this variable. Fortunately, gcc versions < 3.3 do not do any
+// optimizations that break nsCOMPtr.
+
+# define NS_MAY_ALIAS_PTR(t) t* __attribute__((__may_alias__))
+#else
+# define NS_MAY_ALIAS_PTR(t) t*
+#endif
+
+/*
+ * The following three macros (NSCAP_ADDREF, NSCAP_RELEASE, and
+ * NSCAP_LOG_ASSIGNMENT) allow external clients the ability to add logging or
+ * other interesting debug facilities. In fact, if you want |nsCOMPtr| to
+ * participate in the standard logging facility, you provide
+ * (e.g., in "nsISupportsImpl.h") suitable definitions
+ *
+ * #define NSCAP_ADDREF(this, ptr) NS_ADDREF(ptr)
+ * #define NSCAP_RELEASE(this, ptr) NS_RELEASE(ptr)
+ */
+
+#ifndef NSCAP_ADDREF
+# define NSCAP_ADDREF(this, ptr) \
+ mozilla::RefPtrTraits< \
+ typename std::remove_reference<decltype(*ptr)>::type>::AddRef(ptr)
+#endif
+
+#ifndef NSCAP_RELEASE
+# define NSCAP_RELEASE(this, ptr) \
+ mozilla::RefPtrTraits< \
+ typename std::remove_reference<decltype(*ptr)>::type>::Release(ptr)
+#endif
+
+// Clients can define |NSCAP_LOG_ASSIGNMENT| to perform logging.
+#ifdef NSCAP_LOG_ASSIGNMENT
+// Remember that |NSCAP_LOG_ASSIGNMENT| was defined by some client so that we
+// know to instantiate |~nsGetterAddRefs| in turn to note the external
+// assignment into the |nsCOMPtr|.
+# define NSCAP_LOG_EXTERNAL_ASSIGNMENT
+#else
+// ...otherwise, just strip it out of the code
+# define NSCAP_LOG_ASSIGNMENT(this, ptr)
+#endif
+
+#ifndef NSCAP_LOG_RELEASE
+# define NSCAP_LOG_RELEASE(this, ptr)
+#endif
+
+namespace mozilla {
+template <class T>
+class OwningNonNull;
+} // namespace mozilla
+
+template <class T>
+inline already_AddRefed<T> dont_AddRef(T* aRawPtr) {
+ return already_AddRefed<T>(aRawPtr);
+}
+
+template <class T>
+inline already_AddRefed<T>&& dont_AddRef(
+ already_AddRefed<T>&& aAlreadyAddRefedPtr) {
+ return std::move(aAlreadyAddRefedPtr);
+}
+
+/*
+ * An nsCOMPtr_helper transforms commonly called getters into typesafe forms
+ * that are more convenient to call, and more efficient to use with |nsCOMPtr|s.
+ * Good candidates for helpers are |QueryInterface()|, |CreateInstance()|, etc.
+ *
+ * Here are the rules for a helper:
+ * - it implements |operator()| to produce an interface pointer
+ * - (except for its name) |operator()| is a valid [XP]COM `getter'
+ * - the interface pointer that it returns is already |AddRef()|ed (as from
+ * any good getter)
+ * - it matches the type requested with the supplied |nsIID| argument
+ * - its constructor provides an optional |nsresult*| that |operator()| can
+ * fill in with an error when it is executed
+ *
+ * See |class nsGetInterface| for an example.
+ */
+class MOZ_STACK_CLASS nsCOMPtr_helper {
+ public:
+ virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const = 0;
+};
+
+/*
+ * nsQueryInterface could have been implemented as an nsCOMPtr_helper to avoid
+ * adding specialized machinery in nsCOMPtr, but do_QueryInterface is called
+ * often enough that the codesize savings are big enough to warrant the
+ * specialcasing.
+ */
+class MOZ_STACK_CLASS nsQueryInterfaceISupports {
+ public:
+ explicit nsQueryInterfaceISupports(nsISupports* aRawPtr) : mRawPtr(aRawPtr) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const;
+
+ private:
+ nsISupports* MOZ_OWNING_REF mRawPtr;
+};
+
+template <typename T>
+class MOZ_STACK_CLASS nsQueryInterface final
+ : public nsQueryInterfaceISupports {
+ public:
+ explicit nsQueryInterface(T* aRawPtr)
+ : nsQueryInterfaceISupports(ToSupports(aRawPtr)) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const {
+ return nsQueryInterfaceISupports::operator()(aIID, aAnswer);
+ }
+};
+
+class MOZ_STACK_CLASS nsQueryInterfaceISupportsWithError {
+ public:
+ nsQueryInterfaceISupportsWithError(nsISupports* aRawPtr, nsresult* aError)
+ : mRawPtr(aRawPtr), mErrorPtr(aError) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const;
+
+ private:
+ nsISupports* MOZ_OWNING_REF mRawPtr;
+ nsresult* mErrorPtr;
+};
+
+template <typename T>
+class MOZ_STACK_CLASS nsQueryInterfaceWithError final
+ : public nsQueryInterfaceISupportsWithError {
+ public:
+ explicit nsQueryInterfaceWithError(T* aRawPtr, nsresult* aError)
+ : nsQueryInterfaceISupportsWithError(ToSupports(aRawPtr), aError) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const {
+ return nsQueryInterfaceISupportsWithError::operator()(aIID, aAnswer);
+ }
+};
+
+namespace mozilla {
+// PointedToType<> is needed so that do_QueryInterface() will work with a
+// variety of smart pointer types in addition to raw pointers. These types
+// include RefPtr<>, nsCOMPtr<>, and OwningNonNull<>.
+template <class T>
+using PointedToType = std::remove_pointer_t<decltype(&*std::declval<T>())>;
+} // namespace mozilla
+
+template <class T>
+inline nsQueryInterface<mozilla::PointedToType<T>> do_QueryInterface(T aPtr) {
+ return nsQueryInterface<mozilla::PointedToType<T>>(aPtr);
+}
+
+template <class T>
+inline nsQueryInterfaceWithError<mozilla::PointedToType<T>> do_QueryInterface(
+ T aRawPtr, nsresult* aError) {
+ return nsQueryInterfaceWithError<mozilla::PointedToType<T>>(aRawPtr, aError);
+}
+
+template <class T>
+inline void do_QueryInterface(already_AddRefed<T>&) {
+ // This signature exists solely to _stop_ you from doing the bad thing.
+ // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by
+ // someone else is an automatic leak. See bug 8221.
+}
+
+template <class T>
+inline void do_QueryInterface(already_AddRefed<T>&, nsresult*) {
+ // This signature exists solely to _stop_ you from doing the bad thing.
+ // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by
+ // someone else is an automatic leak. See bug 8221.
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Using servicemanager with COMPtrs
+class nsGetServiceByCID final {
+ public:
+ explicit nsGetServiceByCID(const nsCID& aCID) : mCID(aCID) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID&, void**) const;
+
+ private:
+ const nsCID& mCID;
+};
+
+class nsGetServiceByCIDWithError final {
+ public:
+ nsGetServiceByCIDWithError(const nsCID& aCID, nsresult* aErrorPtr)
+ : mCID(aCID), mErrorPtr(aErrorPtr) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID&, void**) const;
+
+ private:
+ const nsCID& mCID;
+ nsresult* mErrorPtr;
+};
+
+class nsGetServiceByContractID final {
+ public:
+ explicit nsGetServiceByContractID(const char* aContractID)
+ : mContractID(aContractID) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID&, void**) const;
+
+ private:
+ const char* mContractID;
+};
+
+class nsGetServiceByContractIDWithError final {
+ public:
+ nsGetServiceByContractIDWithError(const char* aContractID,
+ nsresult* aErrorPtr)
+ : mContractID(aContractID), mErrorPtr(aErrorPtr) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID&, void**) const;
+
+ private:
+ const char* mContractID;
+ nsresult* mErrorPtr;
+};
+
+class nsIWeakReference;
+
+// Weak references
+class MOZ_STACK_CLASS nsQueryReferent final {
+ public:
+ nsQueryReferent(nsIWeakReference* aWeakPtr, nsresult* aError)
+ : mWeakPtr(aWeakPtr), mErrorPtr(aError) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const;
+
+ private:
+ nsIWeakReference* MOZ_NON_OWNING_REF mWeakPtr;
+ nsresult* mErrorPtr;
+};
+
+// template<class T> class nsGetterAddRefs;
+
+// Helper for assert_validity method
+template <class T>
+char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2];
+template <class T>
+char TestForIID(...);
+
+template <class T>
+class MOZ_IS_REFPTR nsCOMPtr final {
+ private:
+ void assign_with_AddRef(T*);
+ template <typename U>
+ void assign_from_qi(const nsQueryInterface<U>, const nsIID&);
+ template <typename U>
+ void assign_from_qi_with_error(const nsQueryInterfaceWithError<U>&,
+ const nsIID&);
+ void assign_from_gs_cid(const nsGetServiceByCID, const nsIID&);
+ void assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&,
+ const nsIID&);
+ void assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&);
+ void assign_from_gs_contractid_with_error(
+ const nsGetServiceByContractIDWithError&, const nsIID&);
+ void assign_from_query_referent(const nsQueryReferent&, const nsIID&);
+ void assign_from_helper(const nsCOMPtr_helper&, const nsIID&);
+ void** begin_assignment();
+
+ void assign_assuming_AddRef(T* aNewPtr) {
+ T* oldPtr = mRawPtr;
+ mRawPtr = aNewPtr;
+ NSCAP_LOG_ASSIGNMENT(this, aNewPtr);
+ NSCAP_LOG_RELEASE(this, oldPtr);
+ if (oldPtr) {
+ NSCAP_RELEASE(this, oldPtr);
+ }
+ }
+
+ private:
+ T* MOZ_OWNING_REF mRawPtr;
+
+ void assert_validity() {
+ static_assert(1 < sizeof(TestForIID<T>(nullptr)),
+ "nsCOMPtr only works "
+ "for types with IIDs. Either use RefPtr; add an IID to "
+ "your type with NS_DECLARE_STATIC_IID_ACCESSOR/"
+ "NS_DEFINE_STATIC_IID_ACCESSOR; or make the nsCOMPtr point "
+ "to a base class with an IID.");
+ }
+
+ public:
+ typedef T element_type;
+
+ ~nsCOMPtr() {
+ NSCAP_LOG_RELEASE(this, mRawPtr);
+ if (mRawPtr) {
+ NSCAP_RELEASE(this, mRawPtr);
+ }
+ }
+
+#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES
+ void Assert_NoQueryNeeded() {
+ if (!mRawPtr) {
+ return;
+ }
+ if constexpr (std::is_same_v<T, nsISupports>) {
+ // FIXME: nsCOMPtr<nsISupports> never asserted this, and it currently
+ // fails...
+ return;
+ }
+ // This can't be defined in terms of do_QueryInterface because
+ // that bans casts from a class to itself.
+ void* out = nullptr;
+ mRawPtr->QueryInterface(NS_GET_TEMPLATE_IID(T), &out);
+ T* query_result = static_cast<T*>(out);
+ MOZ_ASSERT(query_result == mRawPtr, "QueryInterface needed");
+ NS_RELEASE(query_result);
+ }
+
+# define NSCAP_ASSERT_NO_QUERY_NEEDED() Assert_NoQueryNeeded();
+#else
+# define NSCAP_ASSERT_NO_QUERY_NEEDED()
+#endif
+
+ // Constructors
+
+ nsCOMPtr() : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ }
+
+ MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ }
+
+ nsCOMPtr(const nsCOMPtr<T>& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) {
+ assert_validity();
+ if (mRawPtr) {
+ NSCAP_ADDREF(this, mRawPtr);
+ }
+ NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr);
+ }
+
+ template <class U>
+ MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr<U>& aSmartPtr)
+ : mRawPtr(aSmartPtr.get()) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T");
+ assert_validity();
+ if (mRawPtr) {
+ NSCAP_ADDREF(this, mRawPtr);
+ }
+ NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.get());
+ }
+
+ nsCOMPtr(nsCOMPtr<T>&& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) {
+ assert_validity();
+ aSmartPtr.mRawPtr = nullptr;
+ NSCAP_LOG_ASSIGNMENT(this, mRawPtr);
+ }
+
+ template <class U>
+ MOZ_IMPLICIT nsCOMPtr(nsCOMPtr<U>&& aSmartPtr)
+ : mRawPtr(aSmartPtr.forget().template downcast<T>().take()) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T");
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, mRawPtr);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ MOZ_IMPLICIT nsCOMPtr(T* aRawPtr) : mRawPtr(aRawPtr) {
+ assert_validity();
+ if (mRawPtr) {
+ NSCAP_ADDREF(this, mRawPtr);
+ }
+ NSCAP_LOG_ASSIGNMENT(this, aRawPtr);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>& aSmartPtr)
+ : mRawPtr(aSmartPtr.take()) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, mRawPtr);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Construct from |otherComPtr.forget()|.
+ MOZ_IMPLICIT nsCOMPtr(already_AddRefed<T>&& aSmartPtr)
+ : mRawPtr(aSmartPtr.take()) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, mRawPtr);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Construct from |std::move(otherRefPtr)|.
+ template <typename U>
+ MOZ_IMPLICIT nsCOMPtr(RefPtr<U>&& aSmartPtr)
+ : mRawPtr(static_cast<already_AddRefed<T>>(aSmartPtr.forget()).take()) {
+ assert_validity();
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ NSCAP_LOG_ASSIGNMENT(this, mRawPtr);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Construct from |already_AddRefed|.
+ template <typename U>
+ MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>& aSmartPtr)
+ : mRawPtr(static_cast<T*>(aSmartPtr.take())) {
+ assert_validity();
+ // But make sure that U actually inherits from T.
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Construct from |otherComPtr.forget()|.
+ template <typename U>
+ MOZ_IMPLICIT nsCOMPtr(already_AddRefed<U>&& aSmartPtr)
+ : mRawPtr(static_cast<T*>(aSmartPtr.take())) {
+ assert_validity();
+ // But make sure that U actually inherits from T.
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ NSCAP_LOG_ASSIGNMENT(this, static_cast<T*>(mRawPtr));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Construct from |do_QueryInterface(expr)|.
+ template <typename U>
+ MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface<U> aQI) : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_qi(aQI, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_QueryInterface(expr, &rv)|.
+ template <typename U>
+ MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError<U>& aQI)
+ : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_qi_with_error(aQI, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_GetService(cid_expr)|.
+ MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_gs_cid(aGS, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_GetService(cid_expr, &rv)|.
+ MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS)
+ : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_gs_cid_with_error(aGS, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_GetService(contractid_expr)|.
+ MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_gs_contractid(aGS, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_GetService(contractid_expr, &rv)|.
+ MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS)
+ : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_gs_contractid_with_error(aGS, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // Construct from |do_QueryReferent(ptr)|
+ MOZ_IMPLICIT nsCOMPtr(const nsQueryReferent& aQueryReferent)
+ : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_query_referent(aQueryReferent, NS_GET_TEMPLATE_IID(T));
+ }
+
+ // And finally, anything else we might need to construct from can exploit the
+ // nsCOMPtr_helper facility.
+ MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) : mRawPtr(nullptr) {
+ assert_validity();
+ NSCAP_LOG_ASSIGNMENT(this, nullptr);
+ assign_from_helper(aHelper, NS_GET_TEMPLATE_IID(T));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // construct from |mozilla::NotNull|.
+ template <typename I,
+ typename = std::enable_if_t<!std::is_same_v<I, nsCOMPtr<T>> &&
+ std::is_convertible_v<I, nsCOMPtr<T>>>>
+ MOZ_IMPLICIT nsCOMPtr(const mozilla::NotNull<I>& aSmartPtr)
+ : mRawPtr(nsCOMPtr<T>(aSmartPtr.get()).forget().take()) {}
+
+ // construct from |mozilla::MovingNotNull|.
+ template <typename I,
+ typename = std::enable_if_t<!std::is_same_v<I, nsCOMPtr<T>> &&
+ std::is_convertible_v<I, nsCOMPtr<T>>>>
+ MOZ_IMPLICIT nsCOMPtr(mozilla::MovingNotNull<I>&& aSmartPtr)
+ : mRawPtr(
+ nsCOMPtr<T>(std::move(aSmartPtr).unwrapBasePtr()).forget().take()) {
+ }
+
+ // Defined in OwningNonNull.h
+ template <class U>
+ MOZ_IMPLICIT nsCOMPtr(const mozilla::OwningNonNull<U>& aOther);
+
+ // Assignment operators
+
+ nsCOMPtr<T>& operator=(const nsCOMPtr<T>& aRhs) {
+ assign_with_AddRef(aRhs.mRawPtr);
+ return *this;
+ }
+
+ template <class U>
+ nsCOMPtr<T>& operator=(const nsCOMPtr<U>& aRhs) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T");
+ assign_with_AddRef(aRhs.get());
+ return *this;
+ }
+
+ nsCOMPtr<T>& operator=(nsCOMPtr<T>&& aRhs) {
+ assign_assuming_AddRef(aRhs.forget().take());
+ return *this;
+ }
+
+ template <class U>
+ nsCOMPtr<T>& operator=(nsCOMPtr<U>&& aRhs) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U should be a subclass of T");
+ assign_assuming_AddRef(aRhs.forget().template downcast<T>().take());
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ nsCOMPtr<T>& operator=(T* aRhs) {
+ assign_with_AddRef(aRhs);
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ nsCOMPtr<T>& operator=(decltype(nullptr)) {
+ assign_assuming_AddRef(nullptr);
+ return *this;
+ }
+
+ // Assign from |already_AddRefed|.
+ template <typename U>
+ nsCOMPtr<T>& operator=(already_AddRefed<U>& aRhs) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ assign_assuming_AddRef(static_cast<T*>(aRhs.take()));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ // Assign from |otherComPtr.forget()|.
+ template <typename U>
+ nsCOMPtr<T>& operator=(already_AddRefed<U>&& aRhs) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ assign_assuming_AddRef(static_cast<T*>(aRhs.take()));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ // Assign from |std::move(otherRefPtr)|.
+ template <typename U>
+ nsCOMPtr<T>& operator=(RefPtr<U>&& aRhs) {
+ // Make sure that U actually inherits from T
+ static_assert(std::is_base_of<T, U>::value, "U is not a subclass of T");
+ assign_assuming_AddRef(static_cast<T*>(aRhs.forget().take()));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ // Assign from |do_QueryInterface(expr)|.
+ template <typename U>
+ nsCOMPtr<T>& operator=(const nsQueryInterface<U> aRhs) {
+ assign_from_qi(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_QueryInterface(expr, &rv)|.
+ template <typename U>
+ nsCOMPtr<T>& operator=(const nsQueryInterfaceWithError<U>& aRhs) {
+ assign_from_qi_with_error(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_GetService(cid_expr)|.
+ nsCOMPtr<T>& operator=(const nsGetServiceByCID aRhs) {
+ assign_from_gs_cid(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_GetService(cid_expr, &rv)|.
+ nsCOMPtr<T>& operator=(const nsGetServiceByCIDWithError& aRhs) {
+ assign_from_gs_cid_with_error(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_GetService(contractid_expr)|.
+ nsCOMPtr<T>& operator=(const nsGetServiceByContractID aRhs) {
+ assign_from_gs_contractid(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_GetService(contractid_expr, &rv)|.
+ nsCOMPtr<T>& operator=(const nsGetServiceByContractIDWithError& aRhs) {
+ assign_from_gs_contractid_with_error(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // Assign from |do_QueryReferent(ptr)|.
+ nsCOMPtr<T>& operator=(const nsQueryReferent& aRhs) {
+ assign_from_query_referent(aRhs, NS_GET_TEMPLATE_IID(T));
+ return *this;
+ }
+
+ // And finally, anything else we might need to assign from can exploit the
+ // nsCOMPtr_helper facility.
+ nsCOMPtr<T>& operator=(const nsCOMPtr_helper& aRhs) {
+ assign_from_helper(aRhs, NS_GET_TEMPLATE_IID(T));
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ return *this;
+ }
+
+ // Assign from |mozilla::NotNull|.
+ template <typename I,
+ typename = std::enable_if_t<std::is_convertible_v<I, nsCOMPtr<T>>>>
+ nsCOMPtr<T>& operator=(const mozilla::NotNull<I>& aSmartPtr) {
+ assign_assuming_AddRef(nsCOMPtr<T>(aSmartPtr.get()).forget().take());
+ return *this;
+ }
+
+ // Assign from |mozilla::MovingNotNull|.
+ template <typename I,
+ typename = std::enable_if_t<std::is_convertible_v<I, nsCOMPtr<T>>>>
+ nsCOMPtr<T>& operator=(mozilla::MovingNotNull<I>&& aSmartPtr) {
+ assign_assuming_AddRef(
+ nsCOMPtr<T>(std::move(aSmartPtr).unwrapBasePtr()).forget().take());
+ return *this;
+ }
+
+ // Defined in OwningNonNull.h
+ template <class U>
+ nsCOMPtr<T>& operator=(const mozilla::OwningNonNull<U>& aOther);
+
+ // Exchange ownership with |aRhs|; can save a pair of refcount operations.
+ void swap(nsCOMPtr<T>& aRhs) {
+ T* temp = aRhs.mRawPtr;
+ NSCAP_LOG_ASSIGNMENT(&aRhs, mRawPtr);
+ NSCAP_LOG_ASSIGNMENT(this, temp);
+ NSCAP_LOG_RELEASE(this, mRawPtr);
+ NSCAP_LOG_RELEASE(&aRhs, temp);
+ aRhs.mRawPtr = mRawPtr;
+ mRawPtr = temp;
+ // |aRhs| maintains the same invariants, so we don't need to
+ // |NSCAP_ASSERT_NO_QUERY_NEEDED|
+ }
+
+ // Exchange ownership with |aRhs|; can save a pair of refcount operations.
+ void swap(T*& aRhs) {
+ T* temp = aRhs;
+ NSCAP_LOG_ASSIGNMENT(this, temp);
+ NSCAP_LOG_RELEASE(this, mRawPtr);
+ aRhs = reinterpret_cast<T*>(mRawPtr);
+ mRawPtr = temp;
+ NSCAP_ASSERT_NO_QUERY_NEEDED();
+ }
+
+ // Other pointer operators
+
+ // Return the value of mRawPtr and null out mRawPtr. Useful for
+ // already_AddRefed return values.
+ already_AddRefed<T> MOZ_MAY_CALL_AFTER_MUST_RETURN forget() {
+ T* temp = nullptr;
+ swap(temp);
+ return already_AddRefed<T>(temp);
+ }
+
+ // Set the target of aRhs to the value of mRawPtr and null out mRawPtr.
+ // Useful to avoid unnecessary AddRef/Release pairs with "out" parameters
+ // where aRhs bay be a T** or an I** where I is a base class of T.
+ template <typename I>
+ void forget(I** aRhs) {
+ NS_ASSERTION(aRhs, "Null pointer passed to forget!");
+ NSCAP_LOG_RELEASE(this, mRawPtr);
+ *aRhs = get();
+ mRawPtr = nullptr;
+ }
+
+ // Prefer the implicit conversion provided automatically by
+ // |operator T*() const|. Use |get()| to resolve ambiguity or to get a
+ // castable pointer.
+ T* get() const { return reinterpret_cast<T*>(mRawPtr); }
+
+ // Makes an nsCOMPtr act like its underlying raw pointer type whenever it is
+ // used in a context where a raw pointer is expected. It is this operator
+ // that makes an nsCOMPtr substitutable for a raw pointer.
+ //
+ // Prefer the implicit use of this operator to calling |get()|, except where
+ // necessary to resolve ambiguity.
+ operator T*() const& { return get(); }
+
+ // Don't allow implicit conversion of temporary nsCOMPtr to raw pointer,
+ // because the refcount might be one and the pointer will immediately become
+ // invalid.
+ operator T*() const&& = delete;
+
+ // Needed to avoid the deleted operator above
+ explicit operator bool() const { return !!mRawPtr; }
+
+ T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ MOZ_ASSERT(mRawPtr != nullptr,
+ "You can't dereference a NULL nsCOMPtr with operator->().");
+ return get();
+ }
+
+ // These are not intended to be used by clients. See |address_of| below.
+ nsCOMPtr<T>* get_address() { return this; }
+ const nsCOMPtr<T>* get_address() const { return this; }
+
+ public:
+ T& operator*() const {
+ MOZ_ASSERT(mRawPtr != nullptr,
+ "You can't dereference a NULL nsCOMPtr with operator*().");
+ return *get();
+ }
+
+ T** StartAssignment() {
+#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT
+ return reinterpret_cast<T**>(begin_assignment());
+#else
+ assign_assuming_AddRef(nullptr);
+ return reinterpret_cast<T**>(&mRawPtr);
+#endif
+ }
+};
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(nsCOMPtr<T>& aField) {
+ aField = nullptr;
+}
+
+template <typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, nsCOMPtr<T>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags);
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_with_AddRef(T* aRawPtr) {
+ if (aRawPtr) {
+ NSCAP_ADDREF(this, aRawPtr);
+ }
+ assign_assuming_AddRef(aRawPtr);
+}
+
+template <class T>
+template <typename U>
+void nsCOMPtr<T>::assign_from_qi(const nsQueryInterface<U> aQI,
+ const nsIID& aIID) {
+ // Allow QIing to nsISupports from nsISupports as a special-case, since
+ // SameCOMIdentity uses it.
+ static_assert(
+ std::is_same_v<T, nsISupports> ||
+ !(std::is_same_v<T, U> || std::is_base_of<T, U>::value),
+ "don't use do_QueryInterface for compile-time-determinable casts");
+ void* newRawPtr;
+ if (NS_FAILED(aQI(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+template <typename U>
+void nsCOMPtr<T>::assign_from_qi_with_error(
+ const nsQueryInterfaceWithError<U>& aQI, const nsIID& aIID) {
+ static_assert(
+ !(std::is_same_v<T, U> || std::is_base_of<T, U>::value),
+ "don't use do_QueryInterface for compile-time-determinable casts");
+ void* newRawPtr;
+ if (NS_FAILED(aQI(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_gs_cid(const nsGetServiceByCID aGS,
+ const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(aGS(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_gs_cid_with_error(
+ const nsGetServiceByCIDWithError& aGS, const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(aGS(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_gs_contractid(const nsGetServiceByContractID aGS,
+ const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(aGS(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_gs_contractid_with_error(
+ const nsGetServiceByContractIDWithError& aGS, const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(aGS(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_query_referent(
+ const nsQueryReferent& aQueryReferent, const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(aQueryReferent(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void nsCOMPtr<T>::assign_from_helper(const nsCOMPtr_helper& helper,
+ const nsIID& aIID) {
+ void* newRawPtr;
+ if (NS_FAILED(helper(aIID, &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+}
+
+template <class T>
+void** nsCOMPtr<T>::begin_assignment() {
+ assign_assuming_AddRef(nullptr);
+ union {
+ T** mT;
+ void** mVoid;
+ } result;
+ result.mT = &mRawPtr;
+ return result.mVoid;
+}
+
+template <class T>
+inline nsCOMPtr<T>* address_of(nsCOMPtr<T>& aPtr) {
+ return aPtr.get_address();
+}
+
+template <class T>
+inline const nsCOMPtr<T>* address_of(const nsCOMPtr<T>& aPtr) {
+ return aPtr.get_address();
+}
+
+/**
+ * This class is designed to be used for anonymous temporary objects in the
+ * argument list of calls that return COM interface pointers, e.g.,
+ *
+ * nsCOMPtr<IFoo> fooP;
+ * ...->QueryInterface(iid, getter_AddRefs(fooP))
+ *
+ * DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_AddRefs()| instead.
+ *
+ * When initialized with a |nsCOMPtr|, as in the example above, it returns
+ * a |void**|, a |T**|, or an |nsISupports**| as needed, that the outer call
+ * (|QueryInterface| in this case) can fill in.
+ *
+ * This type should be a nested class inside |nsCOMPtr<T>|.
+ */
+template <class T>
+class nsGetterAddRefs {
+ public:
+ explicit nsGetterAddRefs(nsCOMPtr<T>& aSmartPtr)
+ : mTargetSmartPtr(aSmartPtr) {}
+
+#if defined(NSCAP_FEATURE_TEST_DONTQUERY_CASES) || \
+ defined(NSCAP_LOG_EXTERNAL_ASSIGNMENT)
+ ~nsGetterAddRefs() {
+# ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT
+ NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)),
+ mTargetSmartPtr.get());
+# endif
+
+# ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES
+ mTargetSmartPtr.Assert_NoQueryNeeded();
+# endif
+ }
+#endif
+
+ operator void**() {
+ return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment());
+ }
+
+ operator T**() { return mTargetSmartPtr.StartAssignment(); }
+ T*& operator*() { return *(mTargetSmartPtr.StartAssignment()); }
+
+ private:
+ nsCOMPtr<T>& mTargetSmartPtr;
+};
+
+template <>
+class nsGetterAddRefs<nsISupports> {
+ public:
+ explicit nsGetterAddRefs(nsCOMPtr<nsISupports>& aSmartPtr)
+ : mTargetSmartPtr(aSmartPtr) {}
+
+#ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT
+ ~nsGetterAddRefs() {
+ NSCAP_LOG_ASSIGNMENT(reinterpret_cast<void*>(address_of(mTargetSmartPtr)),
+ mTargetSmartPtr.get());
+ }
+#endif
+
+ operator void**() {
+ return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment());
+ }
+
+ operator nsISupports**() { return mTargetSmartPtr.StartAssignment(); }
+ nsISupports*& operator*() { return *(mTargetSmartPtr.StartAssignment()); }
+
+ private:
+ nsCOMPtr<nsISupports>& mTargetSmartPtr;
+};
+
+template <class T>
+inline nsGetterAddRefs<T> getter_AddRefs(nsCOMPtr<T>& aSmartPtr) {
+ return nsGetterAddRefs<T>(aSmartPtr);
+}
+
+template <class T, class DestinationType>
+inline nsresult CallQueryInterface(
+ T* aSource, nsGetterAddRefs<DestinationType> aDestination) {
+ return CallQueryInterface(aSource,
+ static_cast<DestinationType**>(aDestination));
+}
+
+// Comparing two |nsCOMPtr|s
+
+template <class T, class U>
+inline bool operator==(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) {
+ return static_cast<const T*>(aLhs.get()) == static_cast<const U*>(aRhs.get());
+}
+
+template <class T, class U>
+inline bool operator!=(const nsCOMPtr<T>& aLhs, const nsCOMPtr<U>& aRhs) {
+ return static_cast<const T*>(aLhs.get()) != static_cast<const U*>(aRhs.get());
+}
+
+// Comparing an |nsCOMPtr| to a raw pointer
+
+template <class T, class U>
+inline bool operator==(const nsCOMPtr<T>& aLhs, const U* aRhs) {
+ return static_cast<const T*>(aLhs.get()) == aRhs;
+}
+
+template <class T, class U>
+inline bool operator==(const U* aLhs, const nsCOMPtr<T>& aRhs) {
+ return aLhs == static_cast<const T*>(aRhs.get());
+}
+
+template <class T, class U>
+inline bool operator!=(const nsCOMPtr<T>& aLhs, const U* aRhs) {
+ return static_cast<const T*>(aLhs.get()) != aRhs;
+}
+
+template <class T, class U>
+inline bool operator!=(const U* aLhs, const nsCOMPtr<T>& aRhs) {
+ return aLhs != static_cast<const T*>(aRhs.get());
+}
+
+template <class T, class U>
+inline bool operator==(const nsCOMPtr<T>& aLhs, U* aRhs) {
+ return static_cast<const T*>(aLhs.get()) == const_cast<const U*>(aRhs);
+}
+
+template <class T, class U>
+inline bool operator==(U* aLhs, const nsCOMPtr<T>& aRhs) {
+ return const_cast<const U*>(aLhs) == static_cast<const T*>(aRhs.get());
+}
+
+template <class T, class U>
+inline bool operator!=(const nsCOMPtr<T>& aLhs, U* aRhs) {
+ return static_cast<const T*>(aLhs.get()) != const_cast<const U*>(aRhs);
+}
+
+template <class T, class U>
+inline bool operator!=(U* aLhs, const nsCOMPtr<T>& aRhs) {
+ return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get());
+}
+
+// Comparing an |nsCOMPtr| to |nullptr|
+
+template <class T>
+inline bool operator==(const nsCOMPtr<T>& aLhs, decltype(nullptr)) {
+ return aLhs.get() == nullptr;
+}
+
+template <class T>
+inline bool operator==(decltype(nullptr), const nsCOMPtr<T>& aRhs) {
+ return nullptr == aRhs.get();
+}
+
+template <class T>
+inline bool operator!=(const nsCOMPtr<T>& aLhs, decltype(nullptr)) {
+ return aLhs.get() != nullptr;
+}
+
+template <class T>
+inline bool operator!=(decltype(nullptr), const nsCOMPtr<T>& aRhs) {
+ return nullptr != aRhs.get();
+}
+
+// Comparing any two [XP]COM objects for identity
+
+inline bool SameCOMIdentity(nsISupports* aLhs, nsISupports* aRhs) {
+ return nsCOMPtr<nsISupports>(do_QueryInterface(aLhs)) ==
+ nsCOMPtr<nsISupports>(do_QueryInterface(aRhs));
+}
+
+template <class SourceType, class DestinationType>
+inline nsresult CallQueryInterface(nsCOMPtr<SourceType>& aSourcePtr,
+ DestinationType** aDestPtr) {
+ return CallQueryInterface(aSourcePtr.get(), aDestPtr);
+}
+
+template <class T>
+RefPtr<T>::RefPtr(const nsQueryReferent& aQueryReferent) {
+ void* newRawPtr;
+ if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ mRawPtr = static_cast<T*>(newRawPtr);
+}
+
+template <class T>
+RefPtr<T>::RefPtr(const nsCOMPtr_helper& aHelper) {
+ void* newRawPtr;
+ if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ mRawPtr = static_cast<T*>(newRawPtr);
+}
+
+template <class T>
+RefPtr<T>& RefPtr<T>::operator=(const nsQueryReferent& aQueryReferent) {
+ void* newRawPtr;
+ if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+ return *this;
+}
+
+template <class T>
+RefPtr<T>& RefPtr<T>::operator=(const nsCOMPtr_helper& aHelper) {
+ void* newRawPtr;
+ if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) {
+ newRawPtr = nullptr;
+ }
+ assign_assuming_AddRef(static_cast<T*>(newRawPtr));
+ return *this;
+}
+
+template <class T>
+inline already_AddRefed<T> do_AddRef(const nsCOMPtr<T>& aObj) {
+ nsCOMPtr<T> ref(aObj);
+ return ref.forget();
+}
+
+// MOZ_DBG support
+
+template <class T>
+std::ostream& operator<<(std::ostream& aOut, const nsCOMPtr<T>& aObj) {
+ return mozilla::DebugValue(aOut, aObj.get());
+}
+
+// ToRefPtr allows to move an nsCOMPtr<T> into a RefPtr<T>. Be mindful when
+// using this, because usually RefPtr<T> should only be used with concrete T and
+// nsCOMPtr<T> should only be used with XPCOM interface T.
+template <class T>
+RefPtr<T> ToRefPtr(nsCOMPtr<T>&& aObj) {
+ return aObj.forget();
+}
+
+// Integration with ResultExtensions.h
+template <typename R>
+auto ResultRefAsParam(nsCOMPtr<R>& aResult) {
+ return getter_AddRefs(aResult);
+}
+
+namespace mozilla::detail {
+template <typename T>
+struct outparam_as_pointer;
+
+template <typename T>
+struct outparam_as_pointer<nsGetterAddRefs<T>> {
+ using type = T**;
+};
+} // namespace mozilla::detail
+
+#endif // !defined(nsCOMPtr_h___)
diff --git a/xpcom/base/nsCRTGlue.cpp b/xpcom/base/nsCRTGlue.cpp
new file mode 100644
index 0000000000..99289daa63
--- /dev/null
+++ b/xpcom/base/nsCRTGlue.cpp
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCRTGlue.h"
+#include "nsXPCOM.h"
+#include "nsDebug.h"
+#include "prtime.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "mozilla/Sprintf.h"
+
+#ifdef XP_WIN
+# include <io.h>
+# include <windows.h>
+#endif
+
+#ifdef FUZZING_SNAPSHOT
+# include <mozilla/fuzzing/NyxWrapper.h>
+#endif
+
+using namespace mozilla;
+
+const char* NS_strspnp(const char* aDelims, const char* aStr) {
+ const char* d;
+ do {
+ for (d = aDelims; *d != '\0'; ++d) {
+ if (*aStr == *d) {
+ ++aStr;
+ break;
+ }
+ }
+ } while (*d);
+
+ return aStr;
+}
+
+char* NS_strtok(const char* aDelims, char** aStr) {
+ if (!*aStr) {
+ return nullptr;
+ }
+
+ char* ret = (char*)NS_strspnp(aDelims, *aStr);
+
+ if (!*ret) {
+ *aStr = ret;
+ return nullptr;
+ }
+
+ char* i = ret;
+ do {
+ for (const char* d = aDelims; *d != '\0'; ++d) {
+ if (*i == *d) {
+ *i = '\0';
+ *aStr = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *aStr = nullptr;
+ return ret;
+}
+
+uint32_t NS_strlen(const char16_t* aString) {
+ MOZ_ASSERT(aString);
+ const char16_t* end;
+
+ for (end = aString; *end; ++end) {
+ // empty loop
+ }
+
+ return end - aString;
+}
+
+int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB) {
+ while (*aStrB) {
+ int r = *aStrA - *aStrB;
+ if (r) {
+ return r;
+ }
+
+ ++aStrA;
+ ++aStrB;
+ }
+
+ return *aStrA != '\0';
+}
+
+int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen) {
+ while (aLen && *aStrB) {
+ int r = *aStrA - *aStrB;
+ if (r) {
+ return r;
+ }
+
+ ++aStrA;
+ ++aStrB;
+ --aLen;
+ }
+
+ return aLen ? *aStrA != '\0' : 0;
+}
+
+char16_t* NS_xstrdup(const char16_t* aString) {
+ uint32_t len = NS_strlen(aString);
+ return NS_xstrndup(aString, len);
+}
+
+template <typename CharT>
+CharT* NS_xstrndup(const CharT* aString, uint32_t aLen) {
+ auto newBuf = (CharT*)moz_xmalloc((aLen + 1) * sizeof(CharT));
+ memcpy(newBuf, aString, aLen * sizeof(CharT));
+ newBuf[aLen] = '\0';
+ return newBuf;
+}
+
+template char16_t* NS_xstrndup<char16_t>(const char16_t* aString,
+ uint32_t aLen);
+template char* NS_xstrndup<char>(const char* aString, uint32_t aLen);
+
+char* NS_xstrdup(const char* aString) {
+ uint32_t len = strlen(aString);
+ char* str = (char*)moz_xmalloc(len + 1);
+ memcpy(str, aString, len);
+ str[len] = '\0';
+ return str;
+}
+
+// clang-format off
+
+// This table maps uppercase characters to lower case characters;
+// characters that are neither upper nor lower case are unaffected.
+const unsigned char nsLowerUpperUtils::kUpper2Lower[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64,
+
+ // upper band mapped to lower [A-Z] => [a-z]
+ 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
+ 112,113,114,115,116,117,118,119,120,121,122,
+
+ 91, 92, 93, 94, 95,
+ 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
+ 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
+ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
+};
+
+const unsigned char nsLowerUpperUtils::kLower2Upper[256] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96,
+
+ // lower band mapped to upper [a-z] => [A-Z]
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
+
+ 123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
+ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
+};
+
+// clang-format on
+
+bool NS_IsUpper(char aChar) {
+ return aChar != (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar];
+}
+
+bool NS_IsLower(char aChar) {
+ return aChar != (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar];
+}
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+
+void NS_MakeRandomString(char* aBuf, int32_t aBufLen) {
+# define TABLE_SIZE 36
+ static const char table[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+ 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
+ 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+
+ // turn PR_Now() into milliseconds since epoch
+ // and salt rand with that.
+ static unsigned int seed = 0;
+ if (seed == 0) {
+ double fpTime = double(PR_Now());
+ seed =
+ (unsigned int)(fpTime * 1e-6 + 0.5); // use 1e-6, granularity of
+ // PR_Now() on the mac is seconds
+ srand(seed);
+ }
+
+ int32_t i;
+ for (i = 0; i < aBufLen; ++i) {
+ *aBuf++ = table[rand() % TABLE_SIZE];
+ }
+ *aBuf = 0;
+}
+
+#endif
diff --git a/xpcom/base/nsCRTGlue.h b/xpcom/base/nsCRTGlue.h
new file mode 100644
index 0000000000..2bfe47b2a8
--- /dev/null
+++ b/xpcom/base/nsCRTGlue.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCRTGlue_h__
+#define nsCRTGlue_h__
+
+#include "nscore.h"
+
+/**
+ * Scan a string for the first character that is *not* in a set of
+ * delimiters. If the string is only delimiter characters, the end of the
+ * string is returned.
+ *
+ * @param aDelims The set of delimiters (null-terminated)
+ * @param aStr The string to search (null-terminated)
+ */
+const char* NS_strspnp(const char* aDelims, const char* aStr);
+
+/**
+ * Tokenize a string. This function is similar to the strtok function in the
+ * C standard library, but it does not use static variables to maintain state
+ * and is therefore thread and reentrancy-safe.
+ *
+ * Any leading delimiters in str are skipped. Then the string is scanned
+ * until an additional delimiter or end-of-string is found. The final
+ * delimiter is set to '\0'.
+ *
+ * @param aDelims The set of delimiters.
+ * @param aStr The string to search. This is an in-out parameter; it is
+ * reset to the end of the found token + 1, or to the
+ * end-of-string if there are no more tokens.
+ * @return The token. If no token is found (the string is only
+ * delimiter characters), nullptr is returned.
+ */
+char* NS_strtok(const char* aDelims, char** aStr);
+
+/**
+ * "strlen" for char16_t strings
+ */
+uint32_t NS_strlen(const char16_t* aString);
+
+/**
+ * "strcmp" for char16_t strings
+ */
+int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB);
+
+/**
+ * "strncmp" for char16_t strings
+ */
+int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen);
+
+/**
+ * "strdup" for char16_t strings, uses the infallible moz_xmalloc allocator.
+ */
+char16_t* NS_xstrdup(const char16_t* aString);
+
+/**
+ * "strdup", but using the infallible moz_xmalloc allocator.
+ */
+char* NS_xstrdup(const char* aString);
+
+/**
+ * strndup for char16_t or char strings (normal strndup is not available on
+ * windows). This function will ensure that the new string is
+ * null-terminated. Uses the infallible moz_xmalloc allocator.
+ *
+ * CharT may be either char16_t or char.
+ */
+template <typename CharT>
+CharT* NS_xstrndup(const CharT* aString, uint32_t aLen);
+
+// The following case-conversion methods only deal in the ascii repertoire
+// A-Z and a-z
+
+// semi-private data declarations... don't use these directly.
+class nsLowerUpperUtils {
+ public:
+ static const unsigned char kLower2Upper[256];
+ static const unsigned char kUpper2Lower[256];
+};
+
+inline char NS_ToUpper(char aChar) {
+ return (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar];
+}
+
+inline char NS_ToLower(char aChar) {
+ return (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar];
+}
+
+bool NS_IsUpper(char aChar);
+bool NS_IsLower(char aChar);
+
+constexpr bool NS_IsAscii(const char* aString) {
+ while (*aString) {
+ if (0x80 & *aString) {
+ return false;
+ }
+ aString++;
+ }
+ return true;
+}
+
+constexpr bool NS_IsAscii(const char* aString, uint32_t aLength) {
+ const char* end = aString + aLength;
+ while (aString < end) {
+ if (0x80 & *aString) {
+ return false;
+ }
+ aString++;
+ }
+ return true;
+}
+
+constexpr bool NS_IsAsciiWhitespace(char16_t aChar) {
+ return aChar == ' ' || aChar == '\r' || aChar == '\n' || aChar == '\t';
+}
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+void NS_MakeRandomString(char* aBuf, int32_t aBufLen);
+#endif
+
+#define FF '\f'
+#define TAB '\t'
+
+#define CRSTR "\015"
+#define LFSTR "\012"
+#define CRLF "\015\012" /* A CR LF equivalent string */
+
+#if defined(ANDROID)
+// On mobile devices, the file system may be very limited in what it
+// considers valid characters. To avoid errors, sanitize conservatively.
+# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|;,+=[]"
+#else
+// Otherwise, we use the most restrictive filesystem as our default set of
+// illegal filename characters. This is currently Windows.
+# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|"
+#endif
+
+// We also provide a list of all known file path separators for all filesystems.
+// This can be used in replacement of FILE_PATH_SEPARATOR when you need to
+// identify or replace all known path separators.
+#define KNOWN_PATH_SEPARATORS "\\/"
+
+#if defined(XP_MACOSX)
+# define FILE_PATH_SEPARATOR "/"
+#elif defined(XP_WIN)
+# define FILE_PATH_SEPARATOR "\\"
+#elif defined(XP_UNIX)
+# define FILE_PATH_SEPARATOR "/"
+#else
+# error need_to_define_your_file_path_separator_and_maybe_illegal_characters
+#endif
+
+// Not all these control characters are illegal in all OSs, but we don't really
+// want them appearing in filenames
+#define CONTROL_CHARACTERS \
+ "\001\002\003\004\005\006\007" \
+ "\010\011\012\013\014\015\016\017" \
+ "\020\021\022\023\024\025\026\027" \
+ "\030\031\032\033\034\035\036\037" \
+ "\177" \
+ "\200\201\202\203\204\205\206\207" \
+ "\210\211\212\213\214\215\216\217" \
+ "\220\221\222\223\224\225\226\227" \
+ "\230\231\232\233\234\235\236\237"
+
+#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS
+
+#endif // nsCRTGlue_h__
diff --git a/xpcom/base/nsClassInfoImpl.cpp b/xpcom/base/nsClassInfoImpl.cpp
new file mode 100644
index 0000000000..2189863a98
--- /dev/null
+++ b/xpcom/base/nsClassInfoImpl.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIClassInfoImpl.h"
+#include "nsString.h"
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+GenericClassInfo::AddRef() { return 2; }
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+GenericClassInfo::Release() { return 1; }
+
+NS_IMPL_QUERY_INTERFACE(GenericClassInfo, nsIClassInfo)
+
+NS_IMETHODIMP
+GenericClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) {
+ return mData->getinterfaces(aArray);
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetScriptableHelper(nsIXPCScriptable** aHelper) {
+ if (mData->getscriptablehelper) {
+ return mData->getscriptablehelper(aHelper);
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetContractID(nsACString& aContractID) {
+ NS_ERROR("GetContractID not implemented");
+ aContractID.SetIsVoid(true);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetClassDescription(nsACString& aDescription) {
+ aDescription.SetIsVoid(true);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetClassID(nsCID** aClassID) {
+ NS_ERROR("GetClassID not implemented");
+ *aClassID = nullptr;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetFlags(uint32_t* aFlags) {
+ *aFlags = mData->flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GenericClassInfo::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
+ *aClassIDNoAlloc = mData->cid;
+ return NS_OK;
+}
diff --git a/xpcom/base/nsCom.h b/xpcom/base/nsCom.h
new file mode 100644
index 0000000000..b6c3ed0296
--- /dev/null
+++ b/xpcom/base/nsCom.h
@@ -0,0 +1,10 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCom_h__
+#define nsCom_h__
+#include "nscore.h"
+#endif
diff --git a/xpcom/base/nsConsoleMessage.cpp b/xpcom/base/nsConsoleMessage.cpp
new file mode 100644
index 0000000000..f856c9eac6
--- /dev/null
+++ b/xpcom/base/nsConsoleMessage.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Base implementation for console messages.
+ */
+
+#include "nsConsoleMessage.h"
+#include "jsapi.h"
+
+NS_IMPL_ISUPPORTS(nsConsoleMessage, nsIConsoleMessage)
+
+nsConsoleMessage::nsConsoleMessage() : mMicroSecondTimeStamp(0) {}
+
+nsConsoleMessage::nsConsoleMessage(const nsAString& aMessage) {
+ mMicroSecondTimeStamp = JS_Now();
+ mMessage.Assign(aMessage);
+ mIsForwardedFromContentProcess = false;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::GetMessageMoz(nsAString& aMessage) {
+ aMessage = mMessage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::GetLogLevel(uint32_t* aLogLevel) {
+ *aLogLevel = nsConsoleMessage::info;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::GetTimeStamp(int64_t* aTimeStamp) {
+ *aTimeStamp = mMicroSecondTimeStamp / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::GetMicroSecondTimeStamp(int64_t* aTimeStamp) {
+ *aTimeStamp = mMicroSecondTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) {
+ CopyUTF16toUTF8(mMessage, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::GetIsForwardedFromContentProcess(
+ bool* aIsForwardedFromContentProcess) {
+ *aIsForwardedFromContentProcess = mIsForwardedFromContentProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleMessage::SetIsForwardedFromContentProcess(
+ bool aIsForwardedFromContentProcess) {
+ mIsForwardedFromContentProcess = aIsForwardedFromContentProcess;
+ return NS_OK;
+}
diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h
new file mode 100644
index 0000000000..42509707ba
--- /dev/null
+++ b/xpcom/base/nsConsoleMessage.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsconsolemessage_h__
+#define __nsconsolemessage_h__
+
+#include "mozilla/Attributes.h"
+
+#include "nsIConsoleMessage.h"
+#include "nsString.h"
+
+class nsConsoleMessage final : public nsIConsoleMessage {
+ public:
+ nsConsoleMessage();
+ explicit nsConsoleMessage(const nsAString& aMessage);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICONSOLEMESSAGE
+
+ private:
+ ~nsConsoleMessage() = default;
+
+ int64_t mMicroSecondTimeStamp;
+ nsString mMessage;
+ bool mIsForwardedFromContentProcess;
+};
+
+#endif /* __nsconsolemessage_h__ */
diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp
new file mode 100644
index 0000000000..e6b17a6571
--- /dev/null
+++ b/xpcom/base/nsConsoleService.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Maintains a circular buffer of recent messages, and notifies
+ * listeners when new messages are logged.
+ */
+
+/* Threadsafe. */
+
+#include "nsCOMArray.h"
+#include "nsThreadUtils.h"
+
+#include "nsConsoleService.h"
+#include "nsConsoleMessage.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIConsoleListener.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsIScriptError.h"
+#include "nsISupportsPrimitives.h"
+#include "js/friend/ErrorMessages.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+
+#if defined(ANDROID)
+# include <android/log.h>
+# include "mozilla/dom/ContentChild.h"
+# include "mozilla/StaticPrefs_consoleservice.h"
+#endif
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+using namespace mozilla;
+
+NS_IMPL_ADDREF(nsConsoleService)
+NS_IMPL_RELEASE(nsConsoleService)
+NS_IMPL_CLASSINFO(nsConsoleService, nullptr,
+ nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
+ NS_CONSOLESERVICE_CID)
+NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver)
+NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver)
+
+static const bool gLoggingEnabled = true;
+static const bool gLoggingBuffered = true;
+#ifdef XP_WIN
+static bool gLoggingToDebugger = true;
+#endif // XP_WIN
+
+nsConsoleService::MessageElement::~MessageElement() = default;
+
+nsConsoleService::nsConsoleService()
+ : mCurrentSize(0),
+ // XXX grab this from a pref!
+ // hm, but worry about circularity, bc we want to be able to report
+ // prefs errs...
+ mMaximumSize(250),
+ mDeliveringMessage(false),
+ mLock("nsConsoleService.mLock") {
+#ifdef XP_WIN
+ // This environment variable controls whether the console service
+ // should be prevented from putting output to the attached debugger.
+ // It only affects the Windows platform.
+ //
+ // To disable OutputDebugString, set:
+ // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1
+ //
+ const char* disableDebugLoggingVar =
+ getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT");
+ gLoggingToDebugger =
+ !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0');
+#endif // XP_WIN
+}
+
+void nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mLock);
+
+ for (MessageElement* e = mMessages.getFirst(); e != nullptr;) {
+ // Only messages implementing nsIScriptError interface expose the
+ // inner window ID.
+ nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
+ if (!scriptError) {
+ e = e->getNext();
+ continue;
+ }
+ uint64_t innerWindowID;
+ nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
+ if (NS_FAILED(rv) || innerWindowID != innerID) {
+ e = e->getNext();
+ continue;
+ }
+
+ MessageElement* next = e->getNext();
+ e->remove();
+ delete e;
+ mCurrentSize--;
+ MOZ_ASSERT(mCurrentSize < mMaximumSize);
+
+ e = next;
+ }
+}
+
+void nsConsoleService::ClearMessages() {
+ // NB: A lock is not required here as it's only called from |Reset| which
+ // locks for us and from the dtor.
+ while (!mMessages.isEmpty()) {
+ MessageElement* e = mMessages.popFirst();
+ delete e;
+ }
+ mCurrentSize = 0;
+}
+
+nsConsoleService::~nsConsoleService() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ ClearMessages();
+}
+
+class AddConsolePrefWatchers : public Runnable {
+ public:
+ explicit AddConsolePrefWatchers(nsConsoleService* aConsole)
+ : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obs);
+ obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obs->AddObserver(mConsole, "inner-window-destroyed", false);
+
+ if (!gLoggingBuffered) {
+ mConsole->Reset();
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsConsoleService> mConsole;
+};
+
+nsresult nsConsoleService::Init() {
+ NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
+
+ return NS_OK;
+}
+
+nsresult nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage* aMessage,
+ bool* sent) {
+ *sent = false;
+
+ nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage);
+ if (!scriptError) {
+ // Not an nsIScriptError
+ return NS_OK;
+ }
+
+ uint64_t windowID;
+ nsresult rv;
+ rv = scriptError->GetInnerWindowID(&windowID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!windowID) {
+ // Does not set window id
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::WindowGlobalParent> windowGlobalParent =
+ mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID);
+ if (!windowGlobalParent) {
+ // Could not find parent window by id
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::BrowserParent> browserParent =
+ windowGlobalParent->GetBrowserParent();
+ if (!browserParent) {
+ return NS_OK;
+ }
+
+ mozilla::dom::ContentParent* contentParent = browserParent->Manager();
+ if (!contentParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString msg, sourceName, sourceLine;
+ nsCString category;
+ uint32_t lineNum, colNum, flags;
+ uint64_t innerWindowId;
+ bool fromPrivateWindow, fromChromeContext;
+
+ rv = scriptError->GetErrorMessage(msg);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetSourceName(sourceName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetSourceLine(sourceLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = scriptError->GetCategory(getter_Copies(category));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetLineNumber(&lineNum);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetColumnNumber(&colNum);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetIsFromPrivateWindow(&fromPrivateWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetIsFromChromeContext(&fromChromeContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = scriptError->GetInnerWindowID(&innerWindowId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *sent = contentParent->SendScriptError(
+ msg, sourceName, sourceLine, lineNum, colNum, flags, category,
+ fromPrivateWindow, innerWindowId, fromChromeContext);
+ return NS_OK;
+}
+
+namespace {
+
+class LogMessageRunnable : public Runnable {
+ public:
+ LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService)
+ : mozilla::Runnable("LogMessageRunnable"),
+ mMessage(aMessage),
+ mService(aService) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIConsoleMessage> mMessage;
+ RefPtr<nsConsoleService> mService;
+};
+
+NS_IMETHODIMP
+LogMessageRunnable::Run() {
+ // Snapshot of listeners so that we don't reenter this hash during
+ // enumeration.
+ nsCOMArray<nsIConsoleListener> listeners;
+ mService->CollectCurrentListeners(listeners);
+
+ mService->SetIsDelivering();
+
+ for (int32_t i = 0; i < listeners.Count(); ++i) {
+ listeners[i]->Observe(mMessage);
+ }
+
+ mService->SetDoneDelivering();
+
+ return NS_OK;
+}
+
+} // namespace
+
+// nsIConsoleService methods
+NS_IMETHODIMP
+nsConsoleService::LogMessage(nsIConsoleMessage* aMessage) {
+ return LogMessageWithMode(aMessage, nsIConsoleService::OutputToLog);
+}
+
+// This can be called off the main thread.
+nsresult nsConsoleService::LogMessageWithMode(
+ nsIConsoleMessage* aMessage, nsIConsoleService::OutputMode aOutputMode) {
+ if (!aMessage) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!gLoggingEnabled) {
+ return NS_OK;
+ }
+
+ if (NS_IsMainThread() && mDeliveringMessage) {
+ nsCString msg;
+ aMessage->ToString(msg);
+ NS_WARNING(
+ nsPrintfCString(
+ "Reentrancy error: some client attempted to display a message to "
+ "the console while in a console listener. The following message "
+ "was discarded: \"%s\"",
+ msg.get())
+ .get());
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsParentProcess() && NS_IsMainThread()) {
+ // If mMessage is a scriptError with an innerWindowId set,
+ // forward it to the matching ContentParent
+ // This enables logging from parent to content process
+ bool sent;
+ nsresult rv = MaybeForwardScriptError(aMessage, &sent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (sent) {
+ return NS_OK;
+ }
+ }
+
+ RefPtr<LogMessageRunnable> r;
+ nsCOMPtr<nsIConsoleMessage> retiredMessage;
+
+ /*
+ * Lock while updating buffer, and while taking snapshot of
+ * listeners array.
+ */
+ {
+ MutexAutoLock lock(mLock);
+
+#if defined(ANDROID)
+ if (StaticPrefs::consoleservice_logcat() && aOutputMode == OutputToLog) {
+ nsCString msg;
+ aMessage->ToString(msg);
+
+ /** Attempt to use the process name as the log tag. */
+ mozilla::dom::ContentChild* child =
+ mozilla::dom::ContentChild::GetSingleton();
+ nsCString appName;
+ if (child) {
+ child->GetProcessName(appName);
+ } else {
+ appName = "GeckoConsole";
+ }
+
+ uint32_t logLevel = 0;
+ aMessage->GetLogLevel(&logLevel);
+
+ android_LogPriority logPriority = ANDROID_LOG_INFO;
+ switch (logLevel) {
+ case nsIConsoleMessage::debug:
+ logPriority = ANDROID_LOG_DEBUG;
+ break;
+ case nsIConsoleMessage::info:
+ logPriority = ANDROID_LOG_INFO;
+ break;
+ case nsIConsoleMessage::warn:
+ logPriority = ANDROID_LOG_WARN;
+ break;
+ case nsIConsoleMessage::error:
+ logPriority = ANDROID_LOG_ERROR;
+ break;
+ }
+
+ __android_log_print(logPriority, appName.get(), "%s", msg.get());
+ }
+#endif
+#ifdef XP_WIN
+ if (gLoggingToDebugger && IsDebuggerPresent()) {
+ nsString msg;
+ aMessage->GetMessageMoz(msg);
+ msg.Append('\n');
+ OutputDebugStringW(msg.get());
+ }
+#endif
+
+ if (gLoggingBuffered) {
+ MessageElement* e = new MessageElement(aMessage);
+ mMessages.insertBack(e);
+ if (mCurrentSize != mMaximumSize) {
+ mCurrentSize++;
+ } else {
+ MessageElement* p = mMessages.popFirst();
+ MOZ_ASSERT(p);
+ p->swapMessage(retiredMessage);
+ delete p;
+ }
+ }
+
+ if (mListeners.Count() > 0) {
+ r = new LogMessageRunnable(aMessage, this);
+ }
+ }
+
+ if (retiredMessage) {
+ // Release |retiredMessage| on the main thread in case it is an instance of
+ // a mainthread-only class like nsScriptErrorWithStack and we're off the
+ // main thread.
+ NS_ReleaseOnMainThread("nsConsoleService::retiredMessage",
+ retiredMessage.forget());
+ }
+
+ if (r) {
+ // avoid failing in XPCShell tests
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (mainThread) {
+ SchedulerGroup::Dispatch(r.forget());
+ }
+ }
+
+ return NS_OK;
+}
+
+// See nsIConsoleService.idl for more info about this method
+NS_IMETHODIMP
+nsConsoleService::CallFunctionAndLogException(
+ JS::Handle<JS::Value> targetGlobal, JS::HandleValue function, JSContext* cx,
+ JS::MutableHandleValue retval) {
+ if (!targetGlobal.isObject() || !function.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx));
+ if (!contextRealm) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> global(
+ cx, js::CheckedUnwrapDynamic(&targetGlobal.toObject(), cx));
+ if (!global) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException
+ // which will do most of the work required for this function.
+ //
+ // We only have to pick the right global for which we want to flag
+ // the exception against.
+ dom::AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* ccx = jsapi.cx();
+
+ // AutoJSAPI picks `targetGlobal` as execution compartment
+ // whereas we expect to run `function` from the callsites compartment.
+ JSAutoRealm ar(ccx, JS::GetRealmGlobalOrNull(contextRealm));
+
+ JS::RootedValue funVal(ccx, function);
+ if (!JS_WrapValue(ccx, &funVal)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!JS_CallFunctionValue(ccx, nullptr, funVal, JS::HandleValueArray::empty(),
+ retval)) {
+ return NS_ERROR_XPC_JAVASCRIPT_ERROR;
+ }
+
+ return NS_OK;
+}
+
+void nsConsoleService::CollectCurrentListeners(
+ nsCOMArray<nsIConsoleListener>& aListeners) {
+ MutexAutoLock lock(mLock);
+ // XXX When MakeBackInserter(nsCOMArray<T>&) is added, we can do:
+ // AppendToArray(aListeners, mListeners.Values());
+ for (const auto& listener : mListeners.Values()) {
+ aListeners.AppendObject(listener);
+ }
+}
+
+NS_IMETHODIMP
+nsConsoleService::LogStringMessage(const char16_t* aMessage) {
+ if (!gLoggingEnabled) {
+ return NS_OK;
+ }
+
+ RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(
+ aMessage ? nsDependentString(aMessage) : EmptyString()));
+ return LogMessage(msg);
+}
+
+NS_IMETHODIMP
+nsConsoleService::GetMessageArray(
+ nsTArray<RefPtr<nsIConsoleMessage>>& aMessages) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(mLock);
+
+ if (mMessages.isEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mCurrentSize <= mMaximumSize);
+ aMessages.SetCapacity(mCurrentSize);
+
+ for (MessageElement* e = mMessages.getFirst(); e != nullptr;
+ e = e->getNext()) {
+ aMessages.AppendElement(e->Get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleService::RegisterListener(nsIConsoleListener* aListener) {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
+ MOZ_ASSERT(canonical);
+
+ MutexAutoLock lock(mLock);
+ return mListeners.WithEntryHandle(canonical, [&](auto&& entry) {
+ if (entry) {
+ // Reregistering a listener isn't good
+ return NS_ERROR_FAILURE;
+ }
+ entry.Insert(aListener);
+ return NS_OK;
+ });
+}
+
+NS_IMETHODIMP
+nsConsoleService::UnregisterListener(nsIConsoleListener* aListener) {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
+
+ MutexAutoLock lock(mLock);
+
+ return mListeners.Remove(canonical)
+ ? NS_OK
+ // Unregistering a listener that was never registered?
+ : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsConsoleService::Reset() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ /*
+ * Make sure nobody trips into the buffer while it's being reset
+ */
+ MutexAutoLock lock(mLock);
+
+ ClearMessages();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleService::ResetWindow(uint64_t windowInnerId) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ ClearMessagesForWindowID(windowInnerId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // Dump all our messages, in case any are cycle collected.
+ Reset();
+ // We could remove ourselves from the observer service, but it is about to
+ // drop all observers anyways, so why bother.
+ } else if (!strcmp(aTopic, "inner-window-destroyed")) {
+ nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
+ MOZ_ASSERT(supportsInt);
+
+ uint64_t windowId;
+ MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));
+
+ ClearMessagesForWindowID(windowId);
+ } else {
+ MOZ_CRASH();
+ }
+ return NS_OK;
+}
diff --git a/xpcom/base/nsConsoleService.h b/xpcom/base/nsConsoleService.h
new file mode 100644
index 0000000000..fe86f4c0fe
--- /dev/null
+++ b/xpcom/base/nsConsoleService.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsConsoleService class declaration.
+ */
+
+#ifndef __nsconsoleservice_h__
+#define __nsconsoleservice_h__
+
+#include <cstdint>
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Mutex.h"
+
+#include "MainThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsHashKeys.h"
+
+#include "nsIConsoleListener.h"
+#include "nsIConsoleMessage.h"
+#include "nsIConsoleService.h"
+#include "nsIObserver.h"
+#include "nsISupports.h"
+
+template <class T>
+class nsCOMArray;
+
+class nsConsoleService final : public nsIConsoleService, public nsIObserver {
+ public:
+ nsConsoleService();
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICONSOLESERVICE
+ NS_DECL_NSIOBSERVER
+
+ void SetIsDelivering() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mDeliveringMessage);
+ mDeliveringMessage = true;
+ }
+
+ void SetDoneDelivering() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDeliveringMessage);
+ mDeliveringMessage = false;
+ }
+
+ typedef nsInterfaceHashtable<nsISupportsHashKey, nsIConsoleListener>
+ ListenerHash;
+ void CollectCurrentListeners(nsCOMArray<nsIConsoleListener>& aListeners);
+
+ private:
+ class MessageElement : public mozilla::LinkedListElement<MessageElement> {
+ public:
+ explicit MessageElement(nsIConsoleMessage* aMessage) : mMessage(aMessage) {}
+
+ nsIConsoleMessage* Get() { return mMessage.get(); }
+
+ // Swap directly into an nsCOMPtr to avoid spurious refcount
+ // traffic off the main thread in debug builds from
+ // NSCAP_ASSERT_NO_QUERY_NEEDED().
+ void swapMessage(nsCOMPtr<nsIConsoleMessage>& aRetVal) {
+ mMessage.swap(aRetVal);
+ }
+
+ ~MessageElement();
+
+ private:
+ nsCOMPtr<nsIConsoleMessage> mMessage;
+
+ MessageElement(const MessageElement&) = delete;
+ MessageElement& operator=(const MessageElement&) = delete;
+ MessageElement(MessageElement&&) = delete;
+ MessageElement& operator=(MessageElement&&) = delete;
+ };
+
+ ~nsConsoleService();
+
+ nsresult MaybeForwardScriptError(nsIConsoleMessage* aMessage, bool* sent);
+
+ void ClearMessagesForWindowID(const uint64_t innerID);
+ void ClearMessages() MOZ_REQUIRES(mLock);
+
+ mozilla::LinkedList<MessageElement> mMessages MOZ_GUARDED_BY(mLock);
+
+ // The current size of mMessages.
+ uint32_t mCurrentSize MOZ_GUARDED_BY(mLock);
+
+ // The maximum size of mMessages.
+ const uint32_t mMaximumSize;
+
+ // Are we currently delivering a console message on the main thread? If
+ // so, we suppress incoming messages on the main thread only, to avoid
+ // infinite repitition.
+ // Only touched on MainThread
+ bool mDeliveringMessage;
+
+ // Listeners to notify whenever a new message is logged.
+ ListenerHash mListeners MOZ_GUARDED_BY(mLock);
+
+ // To serialize interesting methods.
+ mozilla::Mutex mLock;
+};
+
+#endif /* __nsconsoleservice_h__ */
diff --git a/xpcom/base/nsCrashOnException.cpp b/xpcom/base/nsCrashOnException.cpp
new file mode 100644
index 0000000000..67436c15c4
--- /dev/null
+++ b/xpcom/base/nsCrashOnException.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCrashOnException.h"
+#include "nsCOMPtr.h"
+#include "nsICrashReporter.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+static int ReportException(EXCEPTION_POINTERS* aExceptionInfo) {
+ nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (cr) {
+ cr->WriteMinidumpForException(aExceptionInfo);
+ }
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+XPCOM_API(LRESULT)
+CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam) {
+ MOZ_SEH_TRY { return aWndProc(aHWnd, aMsg, aWParam, aLParam); }
+ MOZ_SEH_EXCEPT(ReportException(GetExceptionInformation())) {
+ ::TerminateProcess(::GetCurrentProcess(), 253);
+ }
+ return 0; // not reached
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/nsCrashOnException.h b/xpcom/base/nsCrashOnException.h
new file mode 100644
index 0000000000..1291cd4961
--- /dev/null
+++ b/xpcom/base/nsCrashOnException.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCrashOnException_h
+#define nsCrashOnException_h
+
+#include <nscore.h>
+#include <windows.h>
+
+namespace mozilla {
+
+// Call a given window procedure, and catch any Win32 exceptions raised from it,
+// and report them as crashes.
+XPCOM_API(LRESULT)
+CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/nsCycleCollectionNoteChild.h b/xpcom/base/nsCycleCollectionNoteChild.h
new file mode 100644
index 0000000000..3e1d345aa3
--- /dev/null
+++ b/xpcom/base/nsCycleCollectionNoteChild.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This header will be included by headers that define refpointer and array
+// classes in order to specialize CC helpers such as ImplCycleCollectionTraverse
+// for them.
+
+#ifndef nsCycleCollectionNoteChild_h__
+#define nsCycleCollectionNoteChild_h__
+
+#include "nsCycleCollectionTraversalCallback.h"
+#include "mozilla/Likely.h"
+
+enum { CycleCollectionEdgeNameArrayFlag = 1 };
+
+// Just a helper for appending "[i]". Didn't want to pull in string headers
+// here.
+void CycleCollectionNoteEdgeNameImpl(
+ nsCycleCollectionTraversalCallback& aCallback, const char* aName,
+ uint32_t aFlags = 0);
+
+// Should be inlined so that in the no-debug-info case this is just a simple
+// if().
+MOZ_ALWAYS_INLINE void CycleCollectionNoteEdgeName(
+ nsCycleCollectionTraversalCallback& aCallback, const char* aName,
+ uint32_t aFlags = 0) {
+ if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) {
+ CycleCollectionNoteEdgeNameImpl(aCallback, aName, aFlags);
+ }
+}
+
+#define NS_CYCLE_COLLECTION_INNERCLASS cycleCollection
+
+#define NS_CYCLE_COLLECTION_INNERNAME _cycleCollectorGlobal
+
+#define NS_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()
+
+template <typename T>
+nsISupports* ToSupports(
+ T* aPtr, typename T::NS_CYCLE_COLLECTION_INNERCLASS* aDummy = 0) {
+ return T::NS_CYCLE_COLLECTION_INNERCLASS::Upcast(aPtr);
+}
+
+// The default implementation of this class template is empty, because it
+// should never be used: see the partial specializations below.
+template <typename T, bool IsXPCOM = std::is_base_of<nsISupports, T>::value>
+struct CycleCollectionNoteChildImpl {};
+
+template <typename T>
+struct CycleCollectionNoteChildImpl<T, true> {
+ static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) {
+ aCallback.NoteXPCOMChild(ToSupports(aChild));
+ }
+};
+
+template <typename T>
+struct CycleCollectionNoteChildImpl<T, false> {
+ static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) {
+ aCallback.NoteNativeChild(aChild, NS_CYCLE_COLLECTION_PARTICIPANT(T));
+ }
+};
+
+// We declare CycleCollectionNoteChild in 3-argument and 4-argument variants,
+// rather than using default arguments, so that forward declarations work
+// regardless of header inclusion order.
+template <typename T>
+inline void CycleCollectionNoteChild(
+ nsCycleCollectionTraversalCallback& aCallback, T* aChild, const char* aName,
+ uint32_t aFlags) {
+ CycleCollectionNoteEdgeName(aCallback, aName, aFlags);
+ CycleCollectionNoteChildImpl<T>::Run(aCallback, aChild);
+}
+
+template <typename T>
+inline void CycleCollectionNoteChild(
+ nsCycleCollectionTraversalCallback& aCallback, T* aChild,
+ const char* aName) {
+ CycleCollectionNoteChild(aCallback, aChild, aName, 0);
+}
+
+#endif // nsCycleCollectionNoteChild_h__
diff --git a/xpcom/base/nsCycleCollectionNoteRootCallback.h b/xpcom/base/nsCycleCollectionNoteRootCallback.h
new file mode 100644
index 0000000000..21dc89394a
--- /dev/null
+++ b/xpcom/base/nsCycleCollectionNoteRootCallback.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCycleCollectionNoteRootCallback_h__
+#define nsCycleCollectionNoteRootCallback_h__
+
+#include "nscore.h"
+
+class nsCycleCollectionParticipant;
+class nsISupports;
+class JSObject;
+
+namespace JS {
+class GCCellPtr;
+}
+
+class nsCycleCollectionNoteRootCallback {
+ public:
+ // aRoot must be canonical (ie the result of QIing to
+ // nsCycleCollectionISupports).
+ NS_IMETHOD_(void)
+ NoteXPCOMRoot(nsISupports* aRoot,
+ nsCycleCollectionParticipant* aParticipant) = 0;
+
+ NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) = 0;
+ NS_IMETHOD_(void)
+ NoteNativeRoot(void* aRoot, nsCycleCollectionParticipant* aParticipant) = 0;
+
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKeyDelegate,
+ JS::GCCellPtr aVal) = 0;
+
+ bool WantAllTraces() const { return mWantAllTraces; }
+
+ protected:
+ nsCycleCollectionNoteRootCallback() : mWantAllTraces(false) {}
+
+ bool mWantAllTraces;
+};
+
+#endif // nsCycleCollectionNoteRootCallback_h__
diff --git a/xpcom/base/nsCycleCollectionParticipant.cpp b/xpcom/base/nsCycleCollectionParticipant.cpp
new file mode 100644
index 0000000000..eaca656e23
--- /dev/null
+++ b/xpcom/base/nsCycleCollectionParticipant.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+NS_IMETHODIMP_(void)
+nsXPCOMCycleCollectionParticipant::Root(void* aPtr) {
+ nsISupports* s = static_cast<nsISupports*>(aPtr);
+ NS_ADDREF(s);
+}
+
+NS_IMETHODIMP_(void)
+nsXPCOMCycleCollectionParticipant::Unroot(void* aPtr) {
+ nsISupports* s = static_cast<nsISupports*>(aPtr);
+ NS_RELEASE(s);
+}
+
+// We define a default trace function because some participants don't need
+// to trace anything, so it is okay for them not to define one.
+NS_IMETHODIMP_(void)
+nsXPCOMCycleCollectionParticipant::Trace(void* aPtr, const TraceCallbacks& aCb,
+ void* aClosure) {}
+
+bool nsXPCOMCycleCollectionParticipant::CheckForRightISupports(
+ nsISupports* aSupports) {
+ nsISupports* foo;
+ aSupports->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
+ reinterpret_cast<void**>(&foo));
+ return aSupports == foo;
+}
diff --git a/xpcom/base/nsCycleCollectionParticipant.h b/xpcom/base/nsCycleCollectionParticipant.h
new file mode 100644
index 0000000000..e4d293f42e
--- /dev/null
+++ b/xpcom/base/nsCycleCollectionParticipant.h
@@ -0,0 +1,1107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCycleCollectionParticipant_h__
+#define nsCycleCollectionParticipant_h__
+
+#include <type_traits>
+#include "js/HeapAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/MacroForEach.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsDebug.h"
+#include "nsID.h"
+#include "nscore.h"
+
+/**
+ * Note: the following two IIDs only differ in one bit in the last byte. This
+ * is a hack and is intentional in order to speed up the comparison inside
+ * NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED.
+ */
+#define NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID \
+ { \
+ 0xc61eac14, 0x5f7a, 0x4481, { \
+ 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5e \
+ } \
+ }
+
+/**
+ * Special IID to get at the base nsISupports for a class. Usually this is the
+ * canonical nsISupports pointer, but in the case of tearoffs for example it is
+ * the base nsISupports pointer of the tearoff. This allow the cycle collector
+ * to have separate nsCycleCollectionParticipant's for tearoffs or aggregated
+ * classes.
+ */
+#define NS_CYCLECOLLECTIONISUPPORTS_IID \
+ { \
+ 0xc61eac14, 0x5f7a, 0x4481, { \
+ 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5f \
+ } \
+ }
+
+namespace mozilla {
+enum class CCReason : uint8_t {
+ NO_REASON,
+
+ // Purple buffer "overflow": enough objects are suspected to be cycle
+ // collectable to trigger an immediate CC.
+ MANY_SUSPECTED,
+
+ // The previous collection was kCCForced ago, and there are at least
+ // kCCForcedPurpleLimit objects suspected of being cycle collectable.
+ TIMED,
+
+ // Run a CC after a GC has completed.
+ GC_FINISHED,
+
+ // Run a slice of a collection.
+ SLICE,
+
+ // Manual reasons are explicitly triggered with a custom listener (as opposed
+ // to exceeding some internal threshold.) If a CC is already in progress,
+ // continue it. Otherwise, start a new one.
+ FIRST_MANUAL_REASON = 128,
+
+ // We want to GC, but must finish any ongoing cycle collection first.
+ GC_WAITING = FIRST_MANUAL_REASON,
+
+ // CC requested via an API. Used by tests.
+ API,
+
+ // Collecting in order to dump the heap.
+ DUMP_HEAP,
+
+ // Low memory situation detected.
+ MEM_PRESSURE,
+
+ // IPC message to a content process to trigger a CC. The original reason is
+ // not tracked.
+ IPC_MESSAGE,
+
+ // Cycle collection on a worker. The triggering reason is not tracked.
+ WORKER,
+
+ // Used for finding leaks.
+ SHUTDOWN
+};
+
+#define FOR_EACH_CCREASON(MACRO) \
+ MACRO(NO_REASON) \
+ MACRO(MANY_SUSPECTED) \
+ MACRO(TIMED) \
+ MACRO(GC_FINISHED) \
+ MACRO(SLICE) \
+ MACRO(GC_WAITING) \
+ MACRO(API) \
+ MACRO(DUMP_HEAP) \
+ MACRO(MEM_PRESSURE) \
+ MACRO(IPC_MESSAGE) \
+ MACRO(WORKER) \
+ MACRO(SHUTDOWN)
+
+static inline bool IsManualCCReason(CCReason reason) {
+ return reason >= CCReason::FIRST_MANUAL_REASON;
+}
+
+static inline const char* CCReasonToString(CCReason aReason) {
+ switch (aReason) {
+#define SET_REASON_STR(name) \
+ case CCReason::name: \
+ return #name; \
+ break;
+ FOR_EACH_CCREASON(SET_REASON_STR)
+#undef SET_REASON_STR
+ default:
+ return "<unknown-reason>";
+ }
+}
+
+} // namespace mozilla
+
+/**
+ * Just holds the IID so NS_GET_IID works.
+ */
+class nsCycleCollectionISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_CYCLECOLLECTIONISUPPORTS_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionISupports,
+ NS_CYCLECOLLECTIONISUPPORTS_IID)
+
+class nsCycleCollectionTraversalCallback;
+class nsISupports;
+class nsWrapperCache;
+
+namespace JS {
+template <class T>
+class Heap;
+template <typename T>
+class TenuredHeap;
+} /* namespace JS */
+
+/*
+ * A struct defining pure virtual methods which are called when tracing cycle
+ * collection paticipants. The appropriate method is called depending on the
+ * type of JS GC thing.
+ */
+struct TraceCallbacks {
+ virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+ virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
+ void* aClosure) const = 0;
+};
+
+/*
+ * An implementation of TraceCallbacks that calls a single function for all JS
+ * GC thing types encountered. Implemented in
+ * nsCycleCollectorTraceJSHelpers.cpp.
+ */
+struct TraceCallbackFunc : public TraceCallbacks {
+ typedef void (*Func)(JS::GCCellPtr aPtr, const char* aName, void* aClosure);
+
+ explicit TraceCallbackFunc(Func aCb) : mCallback(aCb) {}
+
+ virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
+ void* aClosure) const override;
+ virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
+ void* aClosure) const override;
+
+ private:
+ Func mCallback;
+};
+
+/**
+ * Participant implementation classes
+ */
+class NS_NO_VTABLE nsCycleCollectionParticipant {
+ public:
+ using Flags = uint8_t;
+ static constexpr Flags FlagMightSkip = 1u << 0;
+ static constexpr Flags FlagTraverseShouldTrace = 1u << 1;
+ // The object is a single zone js holder if FlagMaybeSingleZoneJSHolder is set
+ // and FlagMultiZoneJSHolder isn't set. This setup is needed so that
+ // inheriting classes can unset single zone behavior.
+ static constexpr Flags FlagMaybeSingleZoneJSHolder = 1u << 2;
+ static constexpr Flags FlagMultiZoneJSHolder = 1u << 3;
+ static constexpr Flags AllFlags = FlagMightSkip | FlagTraverseShouldTrace |
+ FlagMaybeSingleZoneJSHolder |
+ FlagMultiZoneJSHolder;
+
+ constexpr explicit nsCycleCollectionParticipant(Flags aFlags)
+ : mFlags(aFlags) {
+ MOZ_ASSERT((aFlags & ~AllFlags) == 0);
+ }
+
+ NS_IMETHOD TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) = 0;
+
+ nsresult TraverseNativeAndJS(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) {
+ nsresult rv = TraverseNative(aPtr, aCb);
+ if (TraverseShouldTrace()) {
+ // Note, we always call Trace, even if Traverse returned
+ // NS_SUCCESS_INTERRUPTED_TRAVERSE.
+ TraceCallbackFunc noteJsChild(&nsCycleCollectionParticipant::NoteJSChild);
+ Trace(aPtr, noteJsChild, &aCb);
+ }
+ return rv;
+ }
+
+ // Implemented in nsCycleCollectorTraceJSHelpers.cpp.
+ static void NoteJSChild(JS::GCCellPtr aGCThing, const char* aName,
+ void* aClosure);
+
+ NS_IMETHOD_(void) Root(void* aPtr) = 0;
+ NS_IMETHOD_(void) Unlink(void* aPtr) = 0;
+ NS_IMETHOD_(void) Unroot(void* aPtr) = 0;
+ NS_IMETHOD_(const char*) ClassName() = 0;
+
+ NS_IMETHOD_(void)
+ Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) {}
+
+ // CanSkip is called during nsCycleCollector_forgetSkippable. If it returns
+ // true, aPtr is removed from the purple buffer and therefore might be left
+ // out from the cycle collector graph the next time that's constructed (unless
+ // it's reachable in some other way).
+ //
+ // CanSkip is allowed to expand the set of certainly-alive objects by removing
+ // other objects from the purple buffer, marking JS things black (in the GC
+ // sense), and so forth. Furthermore, if aRemovingAllowed is true, this call
+ // is allowed to remove aPtr itself from the purple buffer.
+ //
+ // Things can return true from CanSkip if either they know they have no
+ // outgoing edges at all in the cycle collection graph (because then they
+ // can't be parts of a cycle) or they know for sure they're alive.
+ bool CanSkip(void* aPtr, bool aRemovingAllowed) {
+ return MightSkip() ? CanSkipReal(aPtr, aRemovingAllowed) : false;
+ }
+
+ // CanSkipInCC is called during construction of the initial set of roots for
+ // the cycle collector graph. If it returns true, aPtr is left out of that
+ // set of roots. Note that the set of roots includes whatever is in the
+ // purple buffer (after earlier CanSkip calls) plus various other sources of
+ // roots, so an object can end up having CanSkipInCC called on it even if it
+ // returned true from CanSkip. One example of this would be an object that
+ // can potentially trace JS things.
+ //
+ // CanSkipInCC is allowed to remove other objects from the purple buffer but
+ // should not remove aPtr and should not mark JS things black. It should also
+ // not modify any reference counts.
+ //
+ // Things can return true from CanSkipInCC if either they know they have no
+ // outgoing edges at all in the cycle collection graph or they know for sure
+ // they're alive _and_ none of their outgoing edges are to gray (in the GC
+ // sense) gcthings. See also nsWrapperCache::HasNothingToTrace and
+ // nsWrapperCache::HasKnownLiveWrapperAndDoesNotNeedTracing. The restriction
+ // on not having outgoing edges to gray gcthings is because if we _do_ have
+ // them that means we have a "strong" edge to a JS thing and since we're alive
+ // we need to trace through it and mark keep them alive. Outgoing edges to
+ // C++ things don't matter here, because the criteria for when a CC
+ // participant is considered alive are slightly different for JS and C++
+ // things: JS things are only considered alive when reachable via an edge from
+ // a live thing, while C++ things are also considered alive when their
+ // refcount exceeds the number of edges via which they are reachable.
+ bool CanSkipInCC(void* aPtr) {
+ return MightSkip() ? CanSkipInCCReal(aPtr) : false;
+ }
+
+ // CanSkipThis is called during construction of the cycle collector graph,
+ // when we traverse an edge to aPtr and consider adding it to the graph. If
+ // it returns true, aPtr is not added to the graph.
+ //
+ // CanSkipThis is not allowed to change the liveness or reference count of any
+ // objects.
+ //
+ // Things can return true from CanSkipThis if either they know they have no
+ // outgoing edges at all in the cycle collection graph or they know for sure
+ // they're alive.
+ //
+ // Note that CanSkipThis doesn't have to worry about outgoing edges to gray GC
+ // things, because if this object could have those it already got added to the
+ // graph during root set construction. An object should never have
+ // CanSkipThis called on it if it has outgoing strong references to JS things.
+ bool CanSkipThis(void* aPtr) {
+ return MightSkip() ? CanSkipThisReal(aPtr) : false;
+ }
+
+ NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) = 0;
+
+ bool IsSingleZoneJSHolder() const {
+ return (mFlags & FlagMaybeSingleZoneJSHolder) &&
+ !(mFlags & FlagMultiZoneJSHolder);
+ }
+
+ protected:
+ NS_IMETHOD_(bool) CanSkipReal(void* aPtr, bool aRemovingAllowed) {
+ NS_ASSERTION(false, "Forgot to implement CanSkipReal?");
+ return false;
+ }
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* aPtr) {
+ NS_ASSERTION(false, "Forgot to implement CanSkipInCCReal?");
+ return false;
+ }
+ NS_IMETHOD_(bool) CanSkipThisReal(void* aPtr) {
+ NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?");
+ return false;
+ }
+
+ private:
+ bool MightSkip() const { return mFlags & FlagMightSkip; }
+ bool TraverseShouldTrace() const { return mFlags & FlagTraverseShouldTrace; }
+
+ const Flags mFlags;
+};
+
+class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant {
+ public:
+ constexpr explicit nsScriptObjectTracer(Flags aFlags)
+ : nsCycleCollectionParticipant(aFlags | FlagTraverseShouldTrace) {}
+
+ NS_IMETHOD_(void)
+ Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override = 0;
+};
+
+class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant
+ : public nsScriptObjectTracer {
+ public:
+ constexpr explicit nsXPCOMCycleCollectionParticipant(Flags aFlags)
+ : nsScriptObjectTracer(aFlags) {}
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID)
+
+ NS_IMETHOD_(void) Root(void* aPtr) override;
+ NS_IMETHOD_(void) Unroot(void* aPtr) override;
+
+ NS_IMETHOD_(void)
+ Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override;
+
+ static bool CheckForRightISupports(nsISupports* aSupports);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsXPCOMCycleCollectionParticipant,
+ NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID)
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing a QI to nsXPCOMCycleCollectionParticipant
+///////////////////////////////////////////////////////////////////////////////
+
+#define NS_CYCLE_COLLECTION_CLASSNAME(_class) \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS
+
+// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports
+// are special in that they only differ in their last byte. This allows for the
+// optimization below where we first check the first three words of the IID and
+// if we find a match we check the last word to decide which case we have to
+// deal with.
+#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) \
+ if (TopThreeWordsEquals( \
+ aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant), \
+ NS_GET_IID( \
+ nsCycleCollectionISupports)) && /* The calls to LowWordEquals \
+ here are repeated inside the \
+ if branch. This is due to the \
+ fact that we need to maintain \
+ the if/else chain for these \
+ macros, so that the control \
+ flow never enters the if \
+ branch unless if we're \
+ certain one of the \
+ LowWordEquals() branches will \
+ get executed. */ \
+ (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant)) || \
+ LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports)))) { \
+ if (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \
+ *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \
+ return NS_OK; \
+ } \
+ if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \
+ *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ return NS_OK; \
+ } \
+ /* Avoid warnings about foundInterface being left uninitialized. */ \
+ foundInterface = nullptr; \
+ } else
+
+#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_class) \
+ NS_INTERFACE_MAP_BEGIN(_class) \
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class)
+
+#define NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(_class) \
+ if (rv == NS_OK) return rv; \
+ nsISupports* foundInterface; \
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class)
+
+// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports
+// are special in that they only differ in their last byte. This allows for the
+// optimization below where we first check the first three words of the IID and
+// if we find a match we check the last word to decide which case we have to
+// deal with.
+#define NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(_class) \
+ NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \
+ MOZ_ASSERT(aInstancePtr, "null out param"); \
+ \
+ if (TopThreeWordsEquals(aIID, \
+ NS_GET_IID(nsXPCOMCycleCollectionParticipant), \
+ NS_GET_IID(nsCycleCollectionISupports))) { \
+ if (LowWordEquals(aIID, \
+ NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \
+ *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \
+ return NS_OK; \
+ } \
+ if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \
+ *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ return NS_OK; \
+ } \
+ } \
+ nsresult rv = NS_ERROR_FAILURE;
+
+#define NS_CYCLE_COLLECTION_UPCAST(obj, clazz) \
+ NS_CYCLE_COLLECTION_CLASSNAME(clazz)::Upcast(obj)
+
+#ifdef DEBUG
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) _ptr->CheckForRightParticipant()
+#else
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr)
+#endif
+
+// The default implementation of this class template is empty, because it
+// should never be used: see the partial specializations below.
+template <typename T, bool IsXPCOM = std::is_base_of<nsISupports, T>::value>
+struct DowncastCCParticipantImpl {};
+
+// Specialization for XPCOM CC participants
+template <typename T>
+struct DowncastCCParticipantImpl<T, true> {
+ static T* Run(void* aPtr) {
+ nsISupports* s = static_cast<nsISupports*>(aPtr);
+ MOZ_ASSERT(NS_CYCLE_COLLECTION_CLASSNAME(T)::CheckForRightISupports(s),
+ "not the nsISupports pointer we expect");
+ T* rval = NS_CYCLE_COLLECTION_CLASSNAME(T)::Downcast(s);
+ NS_CHECK_FOR_RIGHT_PARTICIPANT(rval);
+ return rval;
+ }
+};
+
+// Specialization for native CC participants
+template <typename T>
+struct DowncastCCParticipantImpl<T, false> {
+ static T* Run(void* aPtr) { return static_cast<T*>(aPtr); }
+};
+
+template <typename T>
+T* DowncastCCParticipant(void* aPtr) {
+ return DowncastCCParticipantImpl<T>::Run(aPtr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing CanSkip methods
+///////////////////////////////////////////////////////////////////////////////
+
+// See documentation for nsCycleCollectionParticipant::CanSkip for documentation
+// about this method.
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(_class) \
+ NS_IMETHODIMP_(bool) \
+ NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipReal(void* p, \
+ bool aRemovingAllowed) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END \
+ (void)tmp; \
+ return false; \
+ }
+
+// See documentation for nsCycleCollectionParticipant::CanSkipInCC for
+// documentation about this method.
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(_class) \
+ NS_IMETHODIMP_(bool) \
+ NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipInCCReal(void* p) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END \
+ (void)tmp; \
+ return false; \
+ }
+
+// See documentation for nsCycleCollectionParticipant::CanSkipThis for
+// documentation about this method.
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(_class) \
+ NS_IMETHODIMP_(bool) \
+ NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipThisReal(void* p) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END \
+ (void)tmp; \
+ return false; \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing nsCycleCollectionParticipant::Unlink
+//
+// You need to use NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED if you want
+// the base class Unlink version to be called before your own implementation.
+// You can use NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED if you want the
+// base class Unlink to get called after your own implementation. You should
+// never use them together.
+///////////////////////////////////////////////////////////////////////////////
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMETHODIMP_(void) \
+ NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(void* p) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ nsISupports* s = static_cast<nsISupports*>(p); \
+ NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s);
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER(_field) \
+ ImplCycleCollectionUnlink(tmp->_field);
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK(...) \
+ MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER, (), (__VA_ARGS__))
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ (void)tmp; \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(_base_class) \
+ nsISupports* s = static_cast<nsISupports*>(p); \
+ NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); \
+ (void)tmp; \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing nsCycleCollectionParticipant::Traverse
+///////////////////////////////////////////////////////////////////////////////
+
+#define NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, _refcnt) \
+ cb.DescribeRefCountedNode(_refcnt, #_class);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \
+ NS_IMETHODIMP \
+ NS_CYCLE_COLLECTION_CLASSNAME(_class)::TraverseNative( \
+ void* p, nsCycleCollectionTraversalCallback& cb) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get())
+
+// Base class' CC participant should return NS_SUCCESS_INTERRUPTED_TRAVERSE
+// from Traverse if it wants derived classes to not traverse anything from
+// their CC participant.
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \
+ nsISupports* s = static_cast<nsISupports*>(p); \
+ if (NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::TraverseNative(s, cb) == \
+ NS_SUCCESS_INTERRUPTED_TRAVERSE) { \
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE; \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER(_field) \
+ ImplCycleCollectionTraverse(cb, tmp->_field, #_field, 0);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE(...) \
+ MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER, (), (__VA_ARGS__))
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(_field) \
+ CycleCollectionNoteChild(cb, tmp->_field, #_field);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ (void)tmp; \
+ return NS_OK; \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing nsScriptObjectTracer::Trace
+///////////////////////////////////////////////////////////////////////////////
+
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
+ void NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace( \
+ void* p, const TraceCallbacks& aCallbacks, void* aClosure) { \
+ _class* tmp = DowncastCCParticipant<_class>(p);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(_class, _base_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
+ nsISupports* s = static_cast<nsISupports*>(p); \
+ NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Trace(s, aCallbacks, aClosure);
+
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(_field) \
+ aCallbacks.Trace(&tmp->_field, #_field, aClosure);
+
+// NB: The (void)tmp; hack in the TRACE_END macro exists to support
+// implementations that don't need to do anything in their Trace method.
+// Without this hack, some compilers warn about the unused tmp local.
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_END \
+ (void)tmp; \
+ }
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for implementing a concrete nsCycleCollectionParticipant
+///////////////////////////////////////////////////////////////////////////////
+
+// If a class defines a participant, then QIing an instance of that class to
+// nsXPCOMCycleCollectionParticipant should produce that participant.
+#ifdef DEBUG
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \
+ virtual void CheckForRightParticipant()
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \
+ virtual void CheckForRightParticipant() override
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) \
+ { \
+ nsXPCOMCycleCollectionParticipant* p; \
+ CallQueryInterface(this, &p); \
+ MOZ_ASSERT(p == &NS_CYCLE_COLLECTION_INNERNAME, \
+ #_class " should QI to its own CC participant"); \
+ }
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class)
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class)
+#else
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class)
+# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class)
+#endif
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \
+ NS_IMETHOD_(const char*) ClassName() override { return #_class; };
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ public: \
+ NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \
+ override; \
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \
+ NS_IMETHOD_(void) DeleteCycleCollectable(void* p) override { \
+ DowncastCCParticipant<_class>(p)->DeleteCycleCollectable(); \
+ } \
+ static _class* Downcast(nsISupports* s) { \
+ return static_cast<_class*>(static_cast<_base*>(s)); \
+ } \
+ static nsISupports* Upcast(_class* p) { \
+ return NS_ISUPPORTS_CAST(_base*, p); \
+ } \
+ template <typename T> \
+ friend nsISupports* ToSupports(T* p, NS_CYCLE_COLLECTION_INNERCLASS* dummy); \
+ NS_IMETHOD_(void) Unlink(void* p) override;
+
+#define NS_PARTICIPANT_AS(type, participant) \
+ const_cast<type*>(reinterpret_cast<const type*>(participant))
+
+#define NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ static constexpr nsXPCOMCycleCollectionParticipant* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ }
+
+/**
+ * We use this macro to force that classes that inherit from a ccable class and
+ * declare their own participant declare themselves as inherited cc classes.
+ * To avoid possibly unnecessary vtables we only do this checking in debug
+ * builds.
+ */
+#ifdef DEBUG
+# define NOT_INHERITED_CANT_OVERRIDE \
+ virtual void BaseCycleCollectable() final {}
+#else
+# define NOT_INHERITED_CANT_OVERRIDE
+#endif
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsXPCOMCycleCollectionParticipant(aFlags) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _class)
+
+// Cycle collector helper for ambiguous classes that can sometimes be skipped.
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \
+ Flags aFlags = FlagMightSkip) /* We always want skippability. */ \
+ : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _class)
+
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsXPCOMCycleCollectionParticipant(aFlags) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS( \
+ _class, _base) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public nsXPCOMCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \
+ Flags aFlags = FlagMightSkip) /* We always want skippability. */ \
+ : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \
+ NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, \
+ _class)
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED( \
+ _class, _base_class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \
+ Flags aFlags = FlagMightSkip | /* We always want skippability. */ \
+ FlagMultiZoneJSHolder) \
+ : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | FlagMightSkip) { \
+ } \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(_class) \
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class)
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \
+ public: \
+ NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \
+ override; \
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \
+ static _class* Downcast(nsISupports* s) { \
+ return static_cast<_class*>(static_cast<_base_class*>( \
+ NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Downcast(s))); \
+ } \
+ NS_IMETHOD_(void) Unlink(void* p) override;
+
+#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(_class, _base_class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(_class, \
+ _base_class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | \
+ FlagMultiZoneJSHolder) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \
+ }; \
+ NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+// Cycle collector participant declarations.
+
+#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ public: \
+ NS_IMETHOD_(void) Root(void* p) override { \
+ static_cast<_class*>(p)->AddRef(); \
+ } \
+ NS_IMETHOD_(void) Unlink(void* n) override; \
+ NS_IMETHOD_(void) Unroot(void* p) override { \
+ static_cast<_class*>(p)->Release(); \
+ } \
+ NS_IMETHOD TraverseNative(void* n, nsCycleCollectionTraversalCallback& cb) \
+ override; \
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \
+ NS_IMETHOD_(void) DeleteCycleCollectable(void* n) override { \
+ DowncastCCParticipant<_class>(n)->DeleteCycleCollectable(); \
+ } \
+ static _class* Downcast(void* s) { \
+ return DowncastCCParticipant<_class>(s); \
+ } \
+ static void* Upcast(_class* p) { return static_cast<void*>(p); }
+
+#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(_class) \
+ void DeleteCycleCollectable(void) { delete this; } \
+ class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsCycleCollectionParticipant(aFlags) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ static constexpr nsCycleCollectionParticipant* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ } \
+ }; \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(_class) \
+ void DeleteCycleCollectable(void) { delete this; } \
+ class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \
+ Flags aFlags = FlagMightSkip) /* We always want skippability. */ \
+ : nsCycleCollectionParticipant(aFlags | FlagMightSkip) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ static nsCycleCollectionParticipant* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ } \
+ }; \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS_WITH_CUSTOM_DELETE( \
+ _class) \
+ class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \
+ public: \
+ constexpr NS_CYCLE_COLLECTION_INNERCLASS() \
+ : nsCycleCollectionParticipant(true) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+ NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \
+ NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \
+ static nsCycleCollectionParticipant* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ } \
+ }; \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(_class) \
+ void DeleteCycleCollectable(void) { delete this; } \
+ class NS_CYCLE_COLLECTION_INNERCLASS : public nsScriptObjectTracer { \
+ public: \
+ constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \
+ : nsScriptObjectTracer(aFlags) {} \
+ \
+ private: \
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \
+ NS_IMETHOD_(void) \
+ Trace(void* p, const TraceCallbacks& cb, void* closure) override; \
+ static constexpr nsScriptObjectTracer* GetParticipant() { \
+ return &_class::NS_CYCLE_COLLECTION_INNERNAME; \
+ } \
+ }; \
+ static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
+#define NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS _class::NS_CYCLE_COLLECTION_INNERNAME;
+
+// By default JS holders are treated as if they may store pointers to multiple
+// zones, but one may optimize GC handling by using single zone holders.
+#define NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(_class) \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS \
+ _class::NS_CYCLE_COLLECTION_INNERNAME( \
+ nsCycleCollectionParticipant::FlagMaybeSingleZoneJSHolder);
+
+// NB: This is not something you usually want to use. It is here to allow
+// adding things to the CC graph to help debugging via CC logs, but it does not
+// traverse or unlink anything, so it is useless for anything else.
+#define NS_IMPL_CYCLE_COLLECTION_0(_class) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_IMPL_CYCLE_COLLECTION(_class, ...) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// If you are looking for NS_IMPL_CYCLE_COLLECTION_INHERITED_0(_class, _base)
+// you should instead not declare any cycle collected stuff in _class, so it
+// will just inherit the CC declarations from _base.
+
+#define NS_IMPL_CYCLE_COLLECTION_INHERITED(_class, _base, ...) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_CYCLE_COLLECTION_NOTE_EDGE_NAME CycleCollectionNoteEdgeName
+
+/**
+ * Convenience macros for defining nISupports methods in a cycle collected
+ * class.
+ */
+
+#define NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \
+ ...) \
+ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(aClass) \
+ NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \
+ NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper)
+
+#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(aClass, aSuper, ...) \
+ NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \
+ __VA_ARGS__) \
+ NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \
+ NS_IMPL_RELEASE_INHERITED(aClass, aSuper)
+
+#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(aClass, aSuper) \
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(aClass) \
+ NS_INTERFACE_MAP_END_INHERITING(aSuper) \
+ NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \
+ NS_IMPL_RELEASE_INHERITED(aClass, aSuper)
+
+/**
+ * Equivalency of the high three words where two IIDs have the same
+ * top three words but not the same low word.
+ */
+inline bool TopThreeWordsEquals(const nsID& aID, const nsID& aOther1,
+ const nsID& aOther2) {
+ MOZ_ASSERT((((uint32_t*)&aOther1.m0)[0] == ((uint32_t*)&aOther2.m0)[0]) &&
+ (((uint32_t*)&aOther1.m0)[1] == ((uint32_t*)&aOther2.m0)[1]) &&
+ (((uint32_t*)&aOther1.m0)[2] == ((uint32_t*)&aOther2.m0)[2]) &&
+ (((uint32_t*)&aOther1.m0)[3] != ((uint32_t*)&aOther2.m0)[3]));
+
+ return ((((uint32_t*)&aID.m0)[0] == ((uint32_t*)&aOther1.m0)[0]) &&
+ (((uint32_t*)&aID.m0)[1] == ((uint32_t*)&aOther1.m0)[1]) &&
+ (((uint32_t*)&aID.m0)[2] == ((uint32_t*)&aOther1.m0)[2]));
+}
+
+/**
+ * Equivalency of the fourth word where the two IIDs have the same
+ * top three words but not the same low word.
+ */
+inline bool LowWordEquals(const nsID& aID, const nsID& aOther) {
+ return (((uint32_t*)&aID.m0)[3] == ((uint32_t*)&aOther.m0)[3]);
+}
+
+// Template magic to modify JS::Heap without including relevant headers, to
+// prevent excessive header dependency.
+template <typename T>
+inline void ImplCycleCollectionUnlink(JS::Heap<T>& aField) {
+ aField.setNull();
+}
+template <typename T>
+inline void ImplCycleCollectionUnlink(JS::Heap<T*>& aField) {
+ aField = nullptr;
+}
+
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS(...) \
+ MOZ_ASSERT(!IsSingleZoneJSHolder()); \
+ MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK, (), \
+ (__VA_ARGS__))
+
+#define NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(class_, native_members_, \
+ js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \
+ using ::ImplCycleCollectionUnlink; \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \
+ MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( \
+ class_, _base, native_members_, js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, _base) \
+ using ::ImplCycleCollectionUnlink; \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, _base) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \
+ MOZ_FOR_EACH_EXPAND_HELPER native_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(class_, _base) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \
+ MOZ_FOR_EACH_EXPAND_HELPER js_members_) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+template <typename... Elements>
+inline void ImplCycleCollectionUnlink(std::tuple<Elements...>& aField) {
+ std::apply([](auto&&... aArgs) { (ImplCycleCollectionUnlink(aArgs), ...); },
+ aField);
+}
+template <typename... Elements>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ std::tuple<Elements...>& aField, const char* aName, uint32_t aFlags) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ std::apply(
+ [&](auto&&... aArgs) {
+ (ImplCycleCollectionTraverse(aCallback, aArgs, aName, aFlags), ...);
+ },
+ aField);
+}
+
+#endif // nsCycleCollectionParticipant_h__
diff --git a/xpcom/base/nsCycleCollectionTraversalCallback.h b/xpcom/base/nsCycleCollectionTraversalCallback.h
new file mode 100644
index 0000000000..9317a5e23a
--- /dev/null
+++ b/xpcom/base/nsCycleCollectionTraversalCallback.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCycleCollectionTraversalCallback_h__
+#define nsCycleCollectionTraversalCallback_h__
+
+#include <cstdint>
+#include "nscore.h"
+
+class nsCycleCollectionParticipant;
+class nsISupports;
+class JSObject;
+
+namespace JS {
+class GCCellPtr;
+}
+
+class NS_NO_VTABLE nsCycleCollectionTraversalCallback {
+ public:
+ // You must call DescribeRefCountedNode() with an accurate
+ // refcount, otherwise cycle collection will fail, and probably crash.
+ // If the callback cares about objname, it should put
+ // WANT_DEBUG_INFO in mFlags.
+ NS_IMETHOD_(void)
+ DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjName) = 0;
+ // Note, aCompartmentAddress is 0 if it is unknown.
+ NS_IMETHOD_(void)
+ DescribeGCedNode(bool aIsMarked, const char* aObjName,
+ uint64_t aCompartmentAddress = 0) = 0;
+
+ NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) = 0;
+ NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) = 0;
+ NS_IMETHOD_(void)
+ NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) = 0;
+
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aKey, nsISupports* aVal,
+ nsCycleCollectionParticipant* aValParticipant) = 0;
+
+ // Give a name to the edge associated with the next call to
+ // NoteXPCOMChild, NoteJSObject, NoteJSScript, or NoteNativeChild.
+ // Callbacks who care about this should set WANT_DEBUG_INFO in the
+ // flags.
+ NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) = 0;
+
+ enum {
+ // Values for flags:
+
+ // Caller should call NoteNextEdgeName and pass useful objName
+ // to DescribeRefCountedNode and DescribeGCedNode.
+ WANT_DEBUG_INFO = (1 << 0),
+
+ // Caller should not skip objects that we know will be
+ // uncollectable.
+ WANT_ALL_TRACES = (1 << 1)
+ };
+ uint32_t Flags() const { return mFlags; }
+ bool WantDebugInfo() const { return (mFlags & WANT_DEBUG_INFO) != 0; }
+ bool WantAllTraces() const { return (mFlags & WANT_ALL_TRACES) != 0; }
+
+ protected:
+ nsCycleCollectionTraversalCallback() : mFlags(0) {}
+
+ uint32_t mFlags;
+};
+
+#endif // nsCycleCollectionTraversalCallback_h__
diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp
new file mode 100644
index 0000000000..9c885b6ef1
--- /dev/null
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -0,0 +1,4042 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//
+// This file implements a garbage-cycle collector based on the paper
+//
+// Concurrent Cycle Collection in Reference Counted Systems
+// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072
+//
+// We are not using the concurrent or acyclic cases of that paper; so
+// the green, red and orange colors are not used.
+//
+// The collector is based on tracking pointers of four colors:
+//
+// Black nodes are definitely live. If we ever determine a node is
+// black, it's ok to forget about, drop from our records.
+//
+// White nodes are definitely garbage cycles. Once we finish with our
+// scanning, we unlink all the white nodes and expect that by
+// unlinking them they will self-destruct (since a garbage cycle is
+// only keeping itself alive with internal links, by definition).
+//
+// Snow-white is an addition to the original algorithm. A snow-white node
+// has reference count zero and is just waiting for deletion.
+//
+// Grey nodes are being scanned. Nodes that turn grey will turn
+// either black if we determine that they're live, or white if we
+// determine that they're a garbage cycle. After the main collection
+// algorithm there should be no grey nodes.
+//
+// Purple nodes are *candidates* for being scanned. They are nodes we
+// haven't begun scanning yet because they're not old enough, or we're
+// still partway through the algorithm.
+//
+// XPCOM objects participating in garbage-cycle collection are obliged
+// to inform us when they ought to turn purple; that is, when their
+// refcount transitions from N+1 -> N, for nonzero N. Furthermore we
+// require that *after* an XPCOM object has informed us of turning
+// purple, they will tell us when they either transition back to being
+// black (incremented refcount) or are ultimately deleted.
+
+// Incremental cycle collection
+//
+// Beyond the simple state machine required to implement incremental
+// collection, the CC needs to be able to compensate for things the browser
+// is doing during the collection. There are two kinds of problems. For each
+// of these, there are two cases to deal with: purple-buffered C++ objects
+// and JS objects.
+
+// The first problem is that an object in the CC's graph can become garbage.
+// This is bad because the CC touches the objects in its graph at every
+// stage of its operation.
+//
+// All cycle collected C++ objects that die during a cycle collection
+// will end up actually getting deleted by the SnowWhiteKiller. Before
+// the SWK deletes an object, it checks if an ICC is running, and if so,
+// if the object is in the graph. If it is, the CC clears mPointer and
+// mParticipant so it does not point to the raw object any more. Because
+// objects could die any time the CC returns to the mutator, any time the CC
+// accesses a PtrInfo it must perform a null check on mParticipant to
+// ensure the object has not gone away.
+//
+// JS objects don't always run finalizers, so the CC can't remove them from
+// the graph when they die. Fortunately, JS objects can only die during a GC,
+// so if a GC is begun during an ICC, the browser synchronously finishes off
+// the ICC, which clears the entire CC graph. If the GC and CC are scheduled
+// properly, this should be rare.
+//
+// The second problem is that objects in the graph can be changed, say by
+// being addrefed or released, or by having a field updated, after the object
+// has been added to the graph. The problem is that ICC can miss a newly
+// created reference to an object, and end up unlinking an object that is
+// actually alive.
+//
+// The basic idea of the solution, from "An on-the-fly Reference Counting
+// Garbage Collector for Java" by Levanoni and Petrank, is to notice if an
+// object has had an additional reference to it created during the collection,
+// and if so, don't collect it during the current collection. This avoids having
+// to rerun the scan as in Bacon & Rajan 2001.
+//
+// For cycle collected C++ objects, we modify AddRef to place the object in
+// the purple buffer, in addition to Release. Then, in the CC, we treat any
+// objects in the purple buffer as being alive, after graph building has
+// completed. Because they are in the purple buffer, they will be suspected
+// in the next CC, so there's no danger of leaks. This is imprecise, because
+// we will treat as live an object that has been Released but not AddRefed
+// during graph building, but that's probably rare enough that the additional
+// bookkeeping overhead is not worthwhile.
+//
+// For JS objects, the cycle collector is only looking at gray objects. If a
+// gray object is touched during ICC, it will be made black by UnmarkGray.
+// Thus, if a JS object has become black during the ICC, we treat it as live.
+// Merged JS zones have to be handled specially: we scan all zone globals.
+// If any are black, we treat the zone as being black.
+
+// Safety
+//
+// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or
+// purple-unsafe.
+//
+// An nsISupports object is scan-safe if:
+//
+// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though
+// this operation loses ISupports identity (like nsIClassInfo).
+// - Additionally, the operation |traverse| on the resulting
+// nsXPCOMCycleCollectionParticipant does not cause *any* refcount
+// adjustment to occur (no AddRef / Release calls).
+//
+// A non-nsISupports ("native") object is scan-safe by explicitly
+// providing its nsCycleCollectionParticipant.
+//
+// An object is purple-safe if it satisfies the following properties:
+//
+// - The object is scan-safe.
+//
+// When we receive a pointer |ptr| via
+// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We
+// can check the scan-safety, but have no way to ensure the
+// purple-safety; objects must obey, or else the entire system falls
+// apart. Don't involve an object in this scheme if you can't
+// guarantee its purple-safety. The easiest way to ensure that an
+// object is purple-safe is to use nsCycleCollectingAutoRefCnt.
+//
+// When we have a scannable set of purple nodes ready, we begin
+// our walks. During the walks, the nodes we |traverse| should only
+// feed us more scan-safe nodes, and should not adjust the refcounts
+// of those nodes.
+//
+// We do not |AddRef| or |Release| any objects during scanning. We
+// rely on the purple-safety of the roots that call |suspect| to
+// hold, such that we will clear the pointer from the purple buffer
+// entry to the object before it is destroyed. The pointers that are
+// merely scan-safe we hold only for the duration of scanning, and
+// there should be no objects released from the scan-safe set during
+// the scan.
+//
+// We *do* call |Root| and |Unroot| on every white object, on
+// either side of the calls to |Unlink|. This keeps the set of white
+// objects alive during the unlinking.
+//
+
+#if !defined(__MINGW32__)
+# ifdef WIN32
+# include <crtdbg.h>
+# include <errno.h>
+# endif
+#endif
+
+#include "base/process_util.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/HoldDropJSObjects.h"
+/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */
+#include <stdint.h>
+#include <stdio.h>
+
+#include <utility>
+
+#include "js/SliceBudget.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/MruCache.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/SegmentedVector.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCycleCollector.h"
+#include "nsDeque.h"
+#include "nsDumpUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsIConsoleService.h"
+#include "nsICycleCollectorListener.h"
+#include "nsIFile.h"
+#include "nsIMemoryReporter.h"
+#include "nsISerialEventTarget.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+
+struct NurseryPurpleBufferEntry {
+ void* mPtr;
+ nsCycleCollectionParticipant* mParticipant;
+ nsCycleCollectingAutoRefCnt* mRefCnt;
+};
+
+#define NURSERY_PURPLE_BUFFER_SIZE 2048
+bool gNurseryPurpleBufferEnabled = true;
+NurseryPurpleBufferEntry gNurseryPurpleBufferEntry[NURSERY_PURPLE_BUFFER_SIZE];
+uint32_t gNurseryPurpleBufferEntryCount = 0;
+
+void ClearNurseryPurpleBuffer();
+
+static void SuspectUsingNurseryPurpleBuffer(
+ void* aPtr, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ MOZ_ASSERT(gNurseryPurpleBufferEnabled);
+ if (gNurseryPurpleBufferEntryCount == NURSERY_PURPLE_BUFFER_SIZE) {
+ ClearNurseryPurpleBuffer();
+ }
+
+ gNurseryPurpleBufferEntry[gNurseryPurpleBufferEntryCount] = {aPtr, aCp,
+ aRefCnt};
+ ++gNurseryPurpleBufferEntryCount;
+}
+
+// #define COLLECT_TIME_DEBUG
+
+// Enable assertions that are useful for diagnosing errors in graph
+// construction.
+// #define DEBUG_CC_GRAPH
+
+#define DEFAULT_SHUTDOWN_COLLECTIONS 5
+
+// One to do the freeing, then another to detect there is no more work to do.
+#define NORMAL_SHUTDOWN_COLLECTIONS 2
+
+// Cycle collector environment variables
+//
+// MOZ_CC_LOG_ALL: If defined, always log cycle collector heaps.
+//
+// MOZ_CC_LOG_SHUTDOWN: If defined, log cycle collector heaps at shutdown.
+//
+// MOZ_CC_LOG_SHUTDOWN_SKIP: If set to a non-negative integer value n, then
+// skip logging for the first n shutdown CCs. This implies MOZ_CC_LOG_SHUTDOWN.
+// The first log or two are much larger than the rest, so it can be useful to
+// reduce the total size of logs if you know already that the initial logs
+// aren't interesting.
+//
+// MOZ_CC_LOG_THREAD: If set to "main", only automatically log main thread
+// CCs. If set to "worker", only automatically log worker CCs. If set to "all",
+// log either. The default value is "all". This must be used with either
+// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything.
+//
+// MOZ_CC_LOG_PROCESS: If set to "main", only automatically log main process
+// CCs. If set to "content", only automatically log tab CCs. If set to "all",
+// log everything. The default value is "all". This must be used with either
+// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything.
+//
+// MOZ_CC_ALL_TRACES: If set to "all", any cycle collector
+// logging done will be WantAllTraces, which disables
+// various cycle collector optimizations to give a fuller picture of
+// the heap. If set to "shutdown", only shutdown logging will be WantAllTraces.
+// The default is none.
+//
+// MOZ_CC_RUN_DURING_SHUTDOWN: In non-DEBUG or builds, if this is set,
+// run cycle collections at shutdown.
+//
+// MOZ_CC_LOG_DIRECTORY: The directory in which logs are placed (such as
+// logs from MOZ_CC_LOG_ALL and MOZ_CC_LOG_SHUTDOWN, or other uses
+// of nsICycleCollectorListener)
+
+// Various parameters of this collector can be tuned using environment
+// variables.
+
+struct nsCycleCollectorParams {
+ bool mLogAll;
+ bool mLogShutdown;
+ bool mAllTracesAll;
+ bool mAllTracesShutdown;
+ bool mLogThisThread;
+ int32_t mLogShutdownSkip = 0;
+
+ nsCycleCollectorParams()
+ : mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr),
+ mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr),
+ mAllTracesAll(false),
+ mAllTracesShutdown(false) {
+ if (const char* lssEnv = PR_GetEnv("MOZ_CC_LOG_SHUTDOWN_SKIP")) {
+ mLogShutdown = true;
+ nsDependentCString lssString(lssEnv);
+ nsresult rv;
+ int32_t lss = lssString.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv) && lss >= 0) {
+ mLogShutdownSkip = lss;
+ }
+ }
+
+ const char* logThreadEnv = PR_GetEnv("MOZ_CC_LOG_THREAD");
+ bool threadLogging = true;
+ if (logThreadEnv && !!strcmp(logThreadEnv, "all")) {
+ if (NS_IsMainThread()) {
+ threadLogging = !strcmp(logThreadEnv, "main");
+ } else {
+ threadLogging = !strcmp(logThreadEnv, "worker");
+ }
+ }
+
+ const char* logProcessEnv = PR_GetEnv("MOZ_CC_LOG_PROCESS");
+ bool processLogging = true;
+ if (logProcessEnv && !!strcmp(logProcessEnv, "all")) {
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Default:
+ processLogging = !strcmp(logProcessEnv, "main");
+ break;
+ case GeckoProcessType_Content:
+ processLogging = !strcmp(logProcessEnv, "content");
+ break;
+ default:
+ processLogging = false;
+ break;
+ }
+ }
+ mLogThisThread = threadLogging && processLogging;
+
+ const char* allTracesEnv = PR_GetEnv("MOZ_CC_ALL_TRACES");
+ if (allTracesEnv) {
+ if (!strcmp(allTracesEnv, "all")) {
+ mAllTracesAll = true;
+ } else if (!strcmp(allTracesEnv, "shutdown")) {
+ mAllTracesShutdown = true;
+ }
+ }
+ }
+
+ // aShutdownCount is how many shutdown CCs we've started.
+ // For non-shutdown CCs, we'll pass in 0.
+ // For the first shutdown CC, we'll pass in 1.
+ bool LogThisCC(int32_t aShutdownCount) {
+ if (mLogAll) {
+ return mLogThisThread;
+ }
+ if (aShutdownCount == 0 || !mLogShutdown) {
+ return false;
+ }
+ if (aShutdownCount <= mLogShutdownSkip) {
+ return false;
+ }
+ return mLogThisThread;
+ }
+
+ bool AllTracesThisCC(bool aIsShutdown) {
+ return mAllTracesAll || (aIsShutdown && mAllTracesShutdown);
+ }
+};
+
+#ifdef COLLECT_TIME_DEBUG
+class TimeLog {
+ public:
+ TimeLog() : mLastCheckpoint(TimeStamp::Now()) {}
+
+ void Checkpoint(const char* aEvent) {
+ TimeStamp now = TimeStamp::Now();
+ double dur = (now - mLastCheckpoint).ToMilliseconds();
+ if (dur >= 0.5) {
+ printf("cc: %s took %.1fms\n", aEvent, dur);
+ }
+ mLastCheckpoint = now;
+ }
+
+ private:
+ TimeStamp mLastCheckpoint;
+};
+#else
+class TimeLog {
+ public:
+ TimeLog() = default;
+ void Checkpoint(const char* aEvent) {}
+};
+#endif
+
+////////////////////////////////////////////////////////////////////////
+// Base types
+////////////////////////////////////////////////////////////////////////
+
+class PtrInfo;
+
+class EdgePool {
+ public:
+ // EdgePool allocates arrays of void*, primarily to hold PtrInfo*.
+ // However, at the end of a block, the last two pointers are a null
+ // and then a void** pointing to the next block. This allows
+ // EdgePool::Iterators to be a single word but still capable of crossing
+ // block boundaries.
+
+ EdgePool() {
+ mSentinelAndBlocks[0].block = nullptr;
+ mSentinelAndBlocks[1].block = nullptr;
+ }
+
+ ~EdgePool() {
+ MOZ_ASSERT(!mSentinelAndBlocks[0].block && !mSentinelAndBlocks[1].block,
+ "Didn't call Clear()?");
+ }
+
+ void Clear() {
+ EdgeBlock* b = EdgeBlocks();
+ while (b) {
+ EdgeBlock* next = b->Next();
+ delete b;
+ b = next;
+ }
+
+ mSentinelAndBlocks[0].block = nullptr;
+ mSentinelAndBlocks[1].block = nullptr;
+ }
+
+#ifdef DEBUG
+ bool IsEmpty() {
+ return !mSentinelAndBlocks[0].block && !mSentinelAndBlocks[1].block;
+ }
+#endif
+
+ private:
+ struct EdgeBlock;
+ union PtrInfoOrBlock {
+ // Use a union to avoid reinterpret_cast and the ensuing
+ // potential aliasing bugs.
+ PtrInfo* ptrInfo;
+ EdgeBlock* block;
+ };
+ struct EdgeBlock {
+ enum { EdgeBlockSize = 16 * 1024 };
+
+ PtrInfoOrBlock mPointers[EdgeBlockSize];
+ EdgeBlock() {
+ mPointers[EdgeBlockSize - 2].block = nullptr; // sentinel
+ mPointers[EdgeBlockSize - 1].block = nullptr; // next block pointer
+ }
+ EdgeBlock*& Next() { return mPointers[EdgeBlockSize - 1].block; }
+ PtrInfoOrBlock* Start() { return &mPointers[0]; }
+ PtrInfoOrBlock* End() { return &mPointers[EdgeBlockSize - 2]; }
+ };
+
+ // Store the null sentinel so that we can have valid iterators
+ // before adding any edges and without adding any blocks.
+ PtrInfoOrBlock mSentinelAndBlocks[2];
+
+ EdgeBlock*& EdgeBlocks() { return mSentinelAndBlocks[1].block; }
+ EdgeBlock* EdgeBlocks() const { return mSentinelAndBlocks[1].block; }
+
+ public:
+ class Iterator {
+ public:
+ Iterator() : mPointer(nullptr) {}
+ explicit Iterator(PtrInfoOrBlock* aPointer) : mPointer(aPointer) {}
+ Iterator(const Iterator& aOther) = default;
+
+ Iterator& operator++() {
+ if (!mPointer->ptrInfo) {
+ // Null pointer is a sentinel for link to the next block.
+ mPointer = (mPointer + 1)->block->mPointers;
+ }
+ ++mPointer;
+ return *this;
+ }
+
+ PtrInfo* operator*() const {
+ if (!mPointer->ptrInfo) {
+ // Null pointer is a sentinel for link to the next block.
+ return (mPointer + 1)->block->mPointers->ptrInfo;
+ }
+ return mPointer->ptrInfo;
+ }
+ bool operator==(const Iterator& aOther) const {
+ return mPointer == aOther.mPointer;
+ }
+ bool operator!=(const Iterator& aOther) const {
+ return mPointer != aOther.mPointer;
+ }
+
+#ifdef DEBUG_CC_GRAPH
+ bool Initialized() const { return mPointer != nullptr; }
+#endif
+
+ private:
+ PtrInfoOrBlock* mPointer;
+ };
+
+ class Builder;
+ friend class Builder;
+ class Builder {
+ public:
+ explicit Builder(EdgePool& aPool)
+ : mCurrent(&aPool.mSentinelAndBlocks[0]),
+ mBlockEnd(&aPool.mSentinelAndBlocks[0]),
+ mNextBlockPtr(&aPool.EdgeBlocks()) {}
+
+ Iterator Mark() { return Iterator(mCurrent); }
+
+ void Add(PtrInfo* aEdge) {
+ if (mCurrent == mBlockEnd) {
+ EdgeBlock* b = new EdgeBlock();
+ *mNextBlockPtr = b;
+ mCurrent = b->Start();
+ mBlockEnd = b->End();
+ mNextBlockPtr = &b->Next();
+ }
+ (mCurrent++)->ptrInfo = aEdge;
+ }
+
+ private:
+ // mBlockEnd points to space for null sentinel
+ PtrInfoOrBlock* mCurrent;
+ PtrInfoOrBlock* mBlockEnd;
+ EdgeBlock** mNextBlockPtr;
+ };
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ EdgeBlock* b = EdgeBlocks();
+ while (b) {
+ n += aMallocSizeOf(b);
+ b = b->Next();
+ }
+ return n;
+ }
+};
+
+#ifdef DEBUG_CC_GRAPH
+# define CC_GRAPH_ASSERT(b) MOZ_ASSERT(b)
+#else
+# define CC_GRAPH_ASSERT(b)
+#endif
+
+#define CC_TELEMETRY(_name, _value) \
+ do { \
+ if (NS_IsMainThread()) { \
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \
+ } else { \
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \
+ } \
+ } while (0)
+
+enum NodeColor { black, white, grey };
+
+// This structure should be kept as small as possible; we may expect
+// hundreds of thousands of them to be allocated and touched
+// repeatedly during each cycle collection.
+class PtrInfo final {
+ public:
+ // mParticipant knows a more concrete type.
+ void* mPointer;
+ nsCycleCollectionParticipant* mParticipant;
+ uint32_t mColor : 2;
+ uint32_t mInternalRefs : 30;
+ uint32_t mRefCount;
+
+ private:
+ EdgePool::Iterator mFirstChild;
+
+ static const uint32_t kInitialRefCount = UINT32_MAX - 1;
+
+ public:
+ PtrInfo(void* aPointer, nsCycleCollectionParticipant* aParticipant)
+ : mPointer(aPointer),
+ mParticipant(aParticipant),
+ mColor(grey),
+ mInternalRefs(0),
+ mRefCount(kInitialRefCount) {
+ MOZ_ASSERT(aParticipant);
+
+ // We initialize mRefCount to a large non-zero value so
+ // that it doesn't look like a JS object to the cycle collector
+ // in the case where the object dies before being traversed.
+ MOZ_ASSERT(!IsGrayJS() && !IsBlackJS());
+ }
+
+ // Allow NodePool::NodeBlock's constructor to compile.
+ PtrInfo()
+ : mPointer{nullptr},
+ mParticipant{nullptr},
+ mColor{0},
+ mInternalRefs{0},
+ mRefCount{0} {
+ MOZ_ASSERT_UNREACHABLE("should never be called");
+ }
+
+ bool IsGrayJS() const { return mRefCount == 0; }
+
+ bool IsBlackJS() const { return mRefCount == UINT32_MAX; }
+
+ bool WasTraversed() const { return mRefCount != kInitialRefCount; }
+
+ EdgePool::Iterator FirstChild() const {
+ CC_GRAPH_ASSERT(mFirstChild.Initialized());
+ return mFirstChild;
+ }
+
+ // this PtrInfo must be part of a NodePool
+ EdgePool::Iterator LastChild() const {
+ CC_GRAPH_ASSERT((this + 1)->mFirstChild.Initialized());
+ return (this + 1)->mFirstChild;
+ }
+
+ void SetFirstChild(EdgePool::Iterator aFirstChild) {
+ CC_GRAPH_ASSERT(aFirstChild.Initialized());
+ mFirstChild = aFirstChild;
+ }
+
+ // this PtrInfo must be part of a NodePool
+ void SetLastChild(EdgePool::Iterator aLastChild) {
+ CC_GRAPH_ASSERT(aLastChild.Initialized());
+ (this + 1)->mFirstChild = aLastChild;
+ }
+
+ void AnnotatedReleaseAssert(bool aCondition, const char* aMessage);
+};
+
+void PtrInfo::AnnotatedReleaseAssert(bool aCondition, const char* aMessage) {
+ if (aCondition) {
+ return;
+ }
+
+ const char* piName = "Unknown";
+ if (mParticipant) {
+ piName = mParticipant->ClassName();
+ }
+ nsPrintfCString msg("%s, for class %s", aMessage, piName);
+ NS_WARNING(msg.get());
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::CycleCollector,
+ msg);
+
+ MOZ_CRASH();
+}
+
+/**
+ * A structure designed to be used like a linked list of PtrInfo, except
+ * it allocates many PtrInfos at a time.
+ */
+class NodePool {
+ private:
+ // The -2 allows us to use |NodeBlockSize + 1| for |mEntries|, and fit
+ // |mNext|, all without causing slop.
+ enum { NodeBlockSize = 4 * 1024 - 2 };
+
+ struct NodeBlock {
+ // We create and destroy NodeBlock using moz_xmalloc/free rather than new
+ // and delete to avoid calling its constructor and destructor.
+ NodeBlock() : mNext{nullptr} {
+ MOZ_ASSERT_UNREACHABLE("should never be called");
+
+ // Ensure NodeBlock is the right size (see the comment on NodeBlockSize
+ // above).
+ static_assert(
+ sizeof(NodeBlock) == 81904 || // 32-bit; equals 19.996 x 4 KiB pages
+ sizeof(NodeBlock) ==
+ 131048, // 64-bit; equals 31.994 x 4 KiB pages
+ "ill-sized NodeBlock");
+ }
+ ~NodeBlock() { MOZ_ASSERT_UNREACHABLE("should never be called"); }
+
+ NodeBlock* mNext;
+ PtrInfo mEntries[NodeBlockSize + 1]; // +1 to store last child of last node
+ };
+
+ public:
+ NodePool() : mBlocks(nullptr), mLast(nullptr) {}
+
+ ~NodePool() { MOZ_ASSERT(!mBlocks, "Didn't call Clear()?"); }
+
+ void Clear() {
+ NodeBlock* b = mBlocks;
+ while (b) {
+ NodeBlock* n = b->mNext;
+ free(b);
+ b = n;
+ }
+
+ mBlocks = nullptr;
+ mLast = nullptr;
+ }
+
+#ifdef DEBUG
+ bool IsEmpty() { return !mBlocks && !mLast; }
+#endif
+
+ class Builder;
+ friend class Builder;
+ class Builder {
+ public:
+ explicit Builder(NodePool& aPool)
+ : mNextBlock(&aPool.mBlocks), mNext(aPool.mLast), mBlockEnd(nullptr) {
+ MOZ_ASSERT(!aPool.mBlocks && !aPool.mLast, "pool not empty");
+ }
+ PtrInfo* Add(void* aPointer, nsCycleCollectionParticipant* aParticipant) {
+ if (mNext == mBlockEnd) {
+ NodeBlock* block = static_cast<NodeBlock*>(malloc(sizeof(NodeBlock)));
+ if (!block) {
+ return nullptr;
+ }
+
+ *mNextBlock = block;
+ mNext = block->mEntries;
+ mBlockEnd = block->mEntries + NodeBlockSize;
+ block->mNext = nullptr;
+ mNextBlock = &block->mNext;
+ }
+ return new (mozilla::KnownNotNull, mNext++)
+ PtrInfo(aPointer, aParticipant);
+ }
+
+ private:
+ NodeBlock** mNextBlock;
+ PtrInfo*& mNext;
+ PtrInfo* mBlockEnd;
+ };
+
+ class Enumerator;
+ friend class Enumerator;
+ class Enumerator {
+ public:
+ explicit Enumerator(NodePool& aPool)
+ : mFirstBlock(aPool.mBlocks),
+ mCurBlock(nullptr),
+ mNext(nullptr),
+ mBlockEnd(nullptr),
+ mLast(aPool.mLast) {}
+
+ bool IsDone() const { return mNext == mLast; }
+
+ bool AtBlockEnd() const { return mNext == mBlockEnd; }
+
+ PtrInfo* GetNext() {
+ MOZ_ASSERT(!IsDone(), "calling GetNext when done");
+ if (mNext == mBlockEnd) {
+ NodeBlock* nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock;
+ mNext = nextBlock->mEntries;
+ mBlockEnd = mNext + NodeBlockSize;
+ mCurBlock = nextBlock;
+ }
+ return mNext++;
+ }
+
+ private:
+ // mFirstBlock is a reference to allow an Enumerator to be constructed
+ // for an empty graph.
+ NodeBlock*& mFirstBlock;
+ NodeBlock* mCurBlock;
+ // mNext is the next value we want to return, unless mNext == mBlockEnd
+ // NB: mLast is a reference to allow enumerating while building!
+ PtrInfo* mNext;
+ PtrInfo* mBlockEnd;
+ PtrInfo*& mLast;
+ };
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // We don't measure the things pointed to by mEntries[] because those
+ // pointers are non-owning.
+ size_t n = 0;
+ NodeBlock* b = mBlocks;
+ while (b) {
+ n += aMallocSizeOf(b);
+ b = b->mNext;
+ }
+ return n;
+ }
+
+ private:
+ NodeBlock* mBlocks;
+ PtrInfo* mLast;
+};
+
+struct PtrToNodeHashPolicy {
+ using Key = PtrInfo*;
+ using Lookup = void*;
+
+ static js::HashNumber hash(const Lookup& aLookup) {
+ return mozilla::HashGeneric(aLookup);
+ }
+
+ static bool match(const Key& aKey, const Lookup& aLookup) {
+ return aKey->mPointer == aLookup;
+ }
+};
+
+struct WeakMapping {
+ // map and key will be null if the corresponding objects are GC marked
+ PtrInfo* mMap;
+ PtrInfo* mKey;
+ PtrInfo* mKeyDelegate;
+ PtrInfo* mVal;
+};
+
+class CCGraphBuilder;
+
+struct CCGraph {
+ NodePool mNodes;
+ EdgePool mEdges;
+ nsTArray<WeakMapping> mWeakMaps;
+ uint32_t mRootCount;
+
+ private:
+ friend CCGraphBuilder;
+
+ mozilla::HashSet<PtrInfo*, PtrToNodeHashPolicy> mPtrInfoMap;
+
+ bool mOutOfMemory;
+
+ static const uint32_t kInitialMapLength = 16384;
+
+ public:
+ CCGraph()
+ : mRootCount(0), mPtrInfoMap(kInitialMapLength), mOutOfMemory(false) {}
+
+ ~CCGraph() = default;
+
+ void Init() { MOZ_ASSERT(IsEmpty(), "Failed to call CCGraph::Clear"); }
+
+ void Clear() {
+ mNodes.Clear();
+ mEdges.Clear();
+ mWeakMaps.Clear();
+ mRootCount = 0;
+ mPtrInfoMap.clearAndCompact();
+ mOutOfMemory = false;
+ }
+
+#ifdef DEBUG
+ bool IsEmpty() {
+ return mNodes.IsEmpty() && mEdges.IsEmpty() && mWeakMaps.IsEmpty() &&
+ mRootCount == 0 && mPtrInfoMap.empty();
+ }
+#endif
+
+ PtrInfo* FindNode(void* aPtr);
+ void RemoveObjectFromMap(void* aObject);
+
+ uint32_t MapCount() const { return mPtrInfoMap.count(); }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+
+ n += mNodes.SizeOfExcludingThis(aMallocSizeOf);
+ n += mEdges.SizeOfExcludingThis(aMallocSizeOf);
+
+ // We don't measure what the WeakMappings point to, because the
+ // pointers are non-owning.
+ n += mWeakMaps.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ n += mPtrInfoMap.shallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return n;
+ }
+};
+
+PtrInfo* CCGraph::FindNode(void* aPtr) {
+ auto p = mPtrInfoMap.lookup(aPtr);
+ return p ? *p : nullptr;
+}
+
+void CCGraph::RemoveObjectFromMap(void* aObj) {
+ auto p = mPtrInfoMap.lookup(aObj);
+ if (p) {
+ PtrInfo* pinfo = *p;
+ pinfo->mPointer = nullptr;
+ pinfo->mParticipant = nullptr;
+ mPtrInfoMap.remove(p);
+ }
+}
+
+static nsISupports* CanonicalizeXPCOMParticipant(nsISupports* aIn) {
+ nsISupports* out = nullptr;
+ aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
+ reinterpret_cast<void**>(&out));
+ return out;
+}
+
+struct nsPurpleBufferEntry {
+ nsPurpleBufferEntry(void* aObject, nsCycleCollectingAutoRefCnt* aRefCnt,
+ nsCycleCollectionParticipant* aParticipant)
+ : mObject(aObject), mRefCnt(aRefCnt), mParticipant(aParticipant) {}
+
+ nsPurpleBufferEntry(nsPurpleBufferEntry&& aOther)
+ : mObject(nullptr), mRefCnt(nullptr), mParticipant(nullptr) {
+ Swap(aOther);
+ }
+
+ void Swap(nsPurpleBufferEntry& aOther) {
+ std::swap(mObject, aOther.mObject);
+ std::swap(mRefCnt, aOther.mRefCnt);
+ std::swap(mParticipant, aOther.mParticipant);
+ }
+
+ void Clear() {
+ mRefCnt->RemoveFromPurpleBuffer();
+ mRefCnt = nullptr;
+ mObject = nullptr;
+ mParticipant = nullptr;
+ }
+
+ ~nsPurpleBufferEntry() {
+ if (mRefCnt) {
+ mRefCnt->RemoveFromPurpleBuffer();
+ }
+ }
+
+ void* mObject;
+ nsCycleCollectingAutoRefCnt* mRefCnt;
+ nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports
+};
+
+class nsCycleCollector;
+
+struct nsPurpleBuffer {
+ private:
+ uint32_t mCount;
+
+ // Try to match the size of a jemalloc bucket, to minimize slop bytes.
+ // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries'
+ // Segment is 16,372 bytes.
+ // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries'
+ // Segment is 32,760 bytes.
+ static const uint32_t kEntriesPerSegment = 1365;
+ static const size_t kSegmentSize =
+ sizeof(nsPurpleBufferEntry) * kEntriesPerSegment;
+ typedef SegmentedVector<nsPurpleBufferEntry, kSegmentSize,
+ InfallibleAllocPolicy>
+ PurpleBufferVector;
+ PurpleBufferVector mEntries;
+
+ public:
+ nsPurpleBuffer() : mCount(0) {
+ static_assert(
+ sizeof(PurpleBufferVector::Segment) == 16372 || // 32-bit
+ sizeof(PurpleBufferVector::Segment) == 32760 || // 64-bit
+ sizeof(PurpleBufferVector::Segment) == 32744, // 64-bit Windows
+ "ill-sized nsPurpleBuffer::mEntries");
+ }
+
+ ~nsPurpleBuffer() = default;
+
+ // This method compacts mEntries.
+ template <class PurpleVisitor>
+ void VisitEntries(PurpleVisitor& aVisitor) {
+ Maybe<AutoRestore<bool>> ar;
+ if (NS_IsMainThread()) {
+ ar.emplace(gNurseryPurpleBufferEnabled);
+ gNurseryPurpleBufferEnabled = false;
+ ClearNurseryPurpleBuffer();
+ }
+
+ if (mEntries.IsEmpty()) {
+ return;
+ }
+
+ uint32_t oldLength = mEntries.Length();
+ uint32_t keptLength = 0;
+ auto revIter = mEntries.IterFromLast();
+ auto iter = mEntries.Iter();
+ // After iteration this points to the first empty entry.
+ auto firstEmptyIter = mEntries.Iter();
+ auto iterFromLastEntry = mEntries.IterFromLast();
+ for (; !iter.Done(); iter.Next()) {
+ nsPurpleBufferEntry& e = iter.Get();
+ if (e.mObject) {
+ if (!aVisitor.Visit(*this, &e)) {
+ return;
+ }
+ }
+
+ // Visit call above may have cleared the entry, or the entry was empty
+ // already.
+ if (!e.mObject) {
+ // Try to find a non-empty entry from the end of the vector.
+ for (; !revIter.Done(); revIter.Prev()) {
+ nsPurpleBufferEntry& otherEntry = revIter.Get();
+ if (&e == &otherEntry) {
+ break;
+ }
+ if (otherEntry.mObject) {
+ if (!aVisitor.Visit(*this, &otherEntry)) {
+ return;
+ }
+ // Visit may have cleared otherEntry.
+ if (otherEntry.mObject) {
+ e.Swap(otherEntry);
+ revIter.Prev(); // We've swapped this now empty entry.
+ break;
+ }
+ }
+ }
+ }
+
+ // Entry is non-empty even after the Visit call, ensure it is kept
+ // in mEntries.
+ if (e.mObject) {
+ firstEmptyIter.Next();
+ ++keptLength;
+ }
+
+ if (&e == &revIter.Get()) {
+ break;
+ }
+ }
+
+ // There were some empty entries.
+ if (oldLength != keptLength) {
+ // While visiting entries, some new ones were possibly added. This can
+ // happen during CanSkip. Move all such new entries to be after other
+ // entries. Note, we don't call Visit on newly added entries!
+ if (&iterFromLastEntry.Get() != &mEntries.GetLast()) {
+ iterFromLastEntry.Next(); // Now pointing to the first added entry.
+ auto& iterForNewEntries = iterFromLastEntry;
+ while (!iterForNewEntries.Done()) {
+ MOZ_ASSERT(!firstEmptyIter.Done());
+ MOZ_ASSERT(!firstEmptyIter.Get().mObject);
+ firstEmptyIter.Get().Swap(iterForNewEntries.Get());
+ firstEmptyIter.Next();
+ iterForNewEntries.Next();
+ }
+ }
+
+ mEntries.PopLastN(oldLength - keptLength);
+ }
+ }
+
+ void FreeBlocks() {
+ mCount = 0;
+ mEntries.Clear();
+ }
+
+ void SelectPointers(CCGraphBuilder& aBuilder);
+
+ // RemoveSkippable removes entries from the purple buffer synchronously
+ // (1) if !aAsyncSnowWhiteFreeing and nsPurpleBufferEntry::mRefCnt is 0 or
+ // (2) if nsXPCOMCycleCollectionParticipant::CanSkip() for the obj or
+ // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false.
+ // (4) If aRemoveChildlessNodes is true, then any nodes in the purple buffer
+ // that will have no children in the cycle collector graph will also be
+ // removed. CanSkip() may be run on these children.
+ void RemoveSkippable(nsCycleCollector* aCollector, js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing,
+ CC_ForgetSkippableCallback aCb);
+
+ MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt) {
+ nsPurpleBufferEntry entry(aObject, aRefCnt, aCp);
+ Unused << mEntries.Append(std::move(entry));
+ MOZ_ASSERT(!entry.mRefCnt, "Move didn't work!");
+ ++mCount;
+ }
+
+ void Remove(nsPurpleBufferEntry* aEntry) {
+ MOZ_ASSERT(mCount != 0, "must have entries");
+ --mCount;
+ aEntry->Clear();
+ }
+
+ uint32_t Count() const { return mCount; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mEntries.SizeOfExcludingThis(aMallocSizeOf);
+ }
+};
+
+static bool AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot,
+ nsCycleCollectionParticipant* aParti);
+
+struct SelectPointersVisitor {
+ explicit SelectPointersVisitor(CCGraphBuilder& aBuilder)
+ : mBuilder(aBuilder) {}
+
+ bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) {
+ MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer");
+ MOZ_ASSERT(aEntry->mRefCnt->get() != 0,
+ "SelectPointersVisitor: snow-white object in the purple buffer");
+ if (!aEntry->mRefCnt->IsPurple() ||
+ AddPurpleRoot(mBuilder, aEntry->mObject, aEntry->mParticipant)) {
+ aBuffer.Remove(aEntry);
+ }
+ return true;
+ }
+
+ private:
+ CCGraphBuilder& mBuilder;
+};
+
+void nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) {
+ SelectPointersVisitor visitor(aBuilder);
+ VisitEntries(visitor);
+
+ MOZ_ASSERT(mCount == 0, "AddPurpleRoot failed");
+ if (mCount == 0) {
+ FreeBlocks();
+ }
+}
+
+enum ccPhase {
+ IdlePhase,
+ GraphBuildingPhase,
+ ScanAndCollectWhitePhase,
+ CleanupPhase
+};
+
+enum ccIsManual { CCIsNotManual = false, CCIsManual = true };
+
+////////////////////////////////////////////////////////////////////////
+// Top level structure for the cycle collector.
+////////////////////////////////////////////////////////////////////////
+
+using js::SliceBudget;
+
+class JSPurpleBuffer;
+
+class nsCycleCollector : public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ private:
+ bool mActivelyCollecting;
+ bool mFreeingSnowWhite;
+ // mScanInProgress should be false when we're collecting white objects.
+ bool mScanInProgress;
+ CycleCollectorResults mResults;
+ TimeStamp mCollectionStart;
+
+ CycleCollectedJSRuntime* mCCJSRuntime;
+
+ ccPhase mIncrementalPhase;
+ int32_t mShutdownCount = 0;
+ CCGraph mGraph;
+ UniquePtr<CCGraphBuilder> mBuilder;
+ RefPtr<nsCycleCollectorLogger> mLogger;
+
+#ifdef DEBUG
+ nsISerialEventTarget* mEventTarget;
+#endif
+
+ nsCycleCollectorParams mParams;
+
+ uint32_t mWhiteNodeCount;
+
+ CC_BeforeUnlinkCallback mBeforeUnlinkCB;
+ CC_ForgetSkippableCallback mForgetSkippableCB;
+
+ nsPurpleBuffer mPurpleBuf;
+
+ uint32_t mUnmergedNeeded;
+ uint32_t mMergedInARow;
+
+ RefPtr<JSPurpleBuffer> mJSPurpleBuffer;
+
+ private:
+ virtual ~nsCycleCollector();
+
+ public:
+ nsCycleCollector();
+
+ void SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime);
+ void ClearCCJSRuntime();
+
+ void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) {
+ CheckThreadSafety();
+ mBeforeUnlinkCB = aBeforeUnlinkCB;
+ }
+
+ void SetForgetSkippableCallback(
+ CC_ForgetSkippableCallback aForgetSkippableCB) {
+ CheckThreadSafety();
+ mForgetSkippableCB = aForgetSkippableCB;
+ }
+
+ void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt);
+ void SuspectNurseryEntries();
+ uint32_t SuspectedCount();
+ void ForgetSkippable(js::SliceBudget& aBudget, bool aRemoveChildlessNodes,
+ bool aAsyncSnowWhiteFreeing);
+ bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
+ bool FreeSnowWhiteWithBudget(js::SliceBudget& aBudget);
+
+ // This method assumes its argument is already canonicalized.
+ void RemoveObjectFromGraph(void* aPtr);
+
+ void PrepareForGarbageCollection();
+ void FinishAnyCurrentCollection(CCReason aReason);
+
+ bool Collect(CCReason aReason, ccIsManual aIsManual, SliceBudget& aBudget,
+ nsICycleCollectorListener* aManualListener,
+ bool aPreferShorterSlices = false);
+ MOZ_CAN_RUN_SCRIPT
+ void Shutdown(bool aDoCollect);
+
+ bool IsIdle() const { return mIncrementalPhase == IdlePhase; }
+
+ void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ size_t* aObjectSize, size_t* aGraphSize,
+ size_t* aPurpleBufferSize) const;
+
+ JSPurpleBuffer* GetJSPurpleBuffer();
+
+ CycleCollectedJSRuntime* Runtime() { return mCCJSRuntime; }
+
+ private:
+ void CheckThreadSafety();
+ MOZ_CAN_RUN_SCRIPT
+ void ShutdownCollect();
+
+ void FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog);
+ bool IsIncrementalGCInProgress();
+ void FinishAnyIncrementalGCInProgress();
+ bool ShouldMergeZones(ccIsManual aIsManual);
+
+ void BeginCollection(CCReason aReason, ccIsManual aIsManual,
+ nsICycleCollectorListener* aManualListener);
+ void MarkRoots(SliceBudget& aBudget);
+ void ScanRoots(bool aFullySynchGraphBuild);
+ void ScanIncrementalRoots();
+ void ScanWhiteNodes(bool aFullySynchGraphBuild);
+ void ScanBlackNodes();
+ void ScanWeakMaps();
+
+ // returns whether anything was collected
+ bool CollectWhite();
+
+ void CleanupAfterCollection();
+};
+
+NS_IMPL_ISUPPORTS(nsCycleCollector, nsIMemoryReporter)
+
+/**
+ * GraphWalker is templatized over a Visitor class that must provide
+ * the following two methods:
+ *
+ * bool ShouldVisitNode(PtrInfo const *pi);
+ * void VisitNode(PtrInfo *pi);
+ */
+template <class Visitor>
+class GraphWalker {
+ private:
+ Visitor mVisitor;
+
+ void DoWalk(nsDeque<PtrInfo>& aQueue);
+
+ void CheckedPush(nsDeque<PtrInfo>& aQueue, PtrInfo* aPi) {
+ if (!aPi) {
+ MOZ_CRASH();
+ }
+ if (!aQueue.Push(aPi, fallible)) {
+ mVisitor.Failed();
+ }
+ }
+
+ public:
+ void Walk(PtrInfo* aPi);
+ void WalkFromRoots(CCGraph& aGraph);
+ // copy-constructing the visitor should be cheap, and less
+ // indirection than using a reference
+ explicit GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) {}
+};
+
+////////////////////////////////////////////////////////////////////////
+// The static collector struct
+////////////////////////////////////////////////////////////////////////
+
+struct CollectorData {
+ RefPtr<nsCycleCollector> mCollector;
+ CycleCollectedJSContext* mContext;
+};
+
+static MOZ_THREAD_LOCAL(CollectorData*) sCollectorData;
+
+////////////////////////////////////////////////////////////////////////
+// Utility functions
+////////////////////////////////////////////////////////////////////////
+
+static inline void ToParticipant(nsISupports* aPtr,
+ nsXPCOMCycleCollectionParticipant** aCp) {
+ // We use QI to move from an nsISupports to an
+ // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper
+ // object that implements traversal and unlinking logic for the nsISupports
+ // in question.
+ *aCp = nullptr;
+ CallQueryInterface(aPtr, aCp);
+}
+
+static void ToParticipant(void* aParti, nsCycleCollectionParticipant** aCp) {
+ // If the participant is null, this is an nsISupports participant,
+ // so we must QI to get the real participant.
+
+ if (!*aCp) {
+ nsISupports* nsparti = static_cast<nsISupports*>(aParti);
+ MOZ_ASSERT(CanonicalizeXPCOMParticipant(nsparti) == nsparti);
+ nsXPCOMCycleCollectionParticipant* xcp;
+ ToParticipant(nsparti, &xcp);
+ *aCp = xcp;
+ }
+}
+
+template <class Visitor>
+MOZ_NEVER_INLINE void GraphWalker<Visitor>::Walk(PtrInfo* aPi) {
+ nsDeque<PtrInfo> queue;
+ CheckedPush(queue, aPi);
+ DoWalk(queue);
+}
+
+template <class Visitor>
+MOZ_NEVER_INLINE void GraphWalker<Visitor>::WalkFromRoots(CCGraph& aGraph) {
+ nsDeque<PtrInfo> queue;
+ NodePool::Enumerator etor(aGraph.mNodes);
+ for (uint32_t i = 0; i < aGraph.mRootCount; ++i) {
+ CheckedPush(queue, etor.GetNext());
+ }
+ DoWalk(queue);
+}
+
+template <class Visitor>
+MOZ_NEVER_INLINE void GraphWalker<Visitor>::DoWalk(nsDeque<PtrInfo>& aQueue) {
+ // Use a aQueue to match the breadth-first traversal used when we
+ // built the graph, for hopefully-better locality.
+ while (aQueue.GetSize() > 0) {
+ PtrInfo* pi = aQueue.PopFront();
+
+ if (pi->WasTraversed() && mVisitor.ShouldVisitNode(pi)) {
+ mVisitor.VisitNode(pi);
+ for (EdgePool::Iterator child = pi->FirstChild(),
+ child_end = pi->LastChild();
+ child != child_end; ++child) {
+ CheckedPush(aQueue, *child);
+ }
+ }
+ }
+}
+
+struct CCGraphDescriber : public LinkedListElement<CCGraphDescriber> {
+ CCGraphDescriber() : mAddress("0x"), mCnt(0), mType(eUnknown) {}
+
+ enum Type {
+ eRefCountedObject,
+ eGCedObject,
+ eGCMarkedObject,
+ eEdge,
+ eRoot,
+ eGarbage,
+ eUnknown
+ };
+
+ nsCString mAddress;
+ nsCString mName;
+ nsCString mCompartmentOrToAddress;
+ uint32_t mCnt;
+ Type mType;
+};
+
+class LogStringMessageAsync : public DiscardableRunnable {
+ public:
+ explicit LogStringMessageAsync(const nsAString& aMsg)
+ : mozilla::DiscardableRunnable("LogStringMessageAsync"), mMsg(aMsg) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(mMsg.get());
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsString mMsg;
+};
+
+class nsCycleCollectorLogSinkToFile final : public nsICycleCollectorLogSink {
+ public:
+ NS_DECL_ISUPPORTS
+
+ nsCycleCollectorLogSinkToFile()
+ : mProcessIdentifier(base::GetCurrentProcId()),
+ mGCLog("gc-edges"),
+ mCCLog("cc-edges") {}
+
+ NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) override {
+ aIdentifier = mFilenameIdentifier;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) override {
+ mFilenameIdentifier = aIdentifier;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetProcessIdentifier(int32_t* aIdentifier) override {
+ *aIdentifier = mProcessIdentifier;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) override {
+ mProcessIdentifier = aIdentifier;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetGcLog(nsIFile** aPath) override {
+ NS_IF_ADDREF(*aPath = mGCLog.mFile);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetCcLog(nsIFile** aPath) override {
+ NS_IF_ADDREF(*aPath = mCCLog.mFile);
+ return NS_OK;
+ }
+
+ NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) override {
+ nsresult rv;
+
+ if (mGCLog.mStream || mCCLog.mStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = OpenLog(&mGCLog);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aGCLog = mGCLog.mStream;
+
+ rv = OpenLog(&mCCLog);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aCCLog = mCCLog.mStream;
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD CloseGCLog() override {
+ if (!mGCLog.mStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ CloseLog(&mGCLog, u"Garbage"_ns);
+ return NS_OK;
+ }
+
+ NS_IMETHOD CloseCCLog() override {
+ if (!mCCLog.mStream) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ CloseLog(&mCCLog, u"Cycle"_ns);
+ return NS_OK;
+ }
+
+ private:
+ ~nsCycleCollectorLogSinkToFile() {
+ if (mGCLog.mStream) {
+ MozillaUnRegisterDebugFILE(mGCLog.mStream);
+ fclose(mGCLog.mStream);
+ }
+ if (mCCLog.mStream) {
+ MozillaUnRegisterDebugFILE(mCCLog.mStream);
+ fclose(mCCLog.mStream);
+ }
+ }
+
+ struct FileInfo {
+ const char* const mPrefix;
+ nsCOMPtr<nsIFile> mFile;
+ FILE* mStream;
+
+ explicit FileInfo(const char* aPrefix)
+ : mPrefix(aPrefix), mStream(nullptr) {}
+ };
+
+ /**
+ * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in
+ * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing
+ * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll
+ * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so
+ * on.
+ */
+ already_AddRefed<nsIFile> CreateTempFile(const char* aPrefix) {
+ nsPrintfCString filename("%s.%d%s%s.log", aPrefix, mProcessIdentifier,
+ mFilenameIdentifier.IsEmpty() ? "" : ".",
+ NS_ConvertUTF16toUTF8(mFilenameIdentifier).get());
+
+ // Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from
+ // the fallback directories in OpenTempFile. We don't use an nsCOMPtr
+ // here because OpenTempFile uses an in/out param and getter_AddRefs
+ // wouldn't work.
+ nsIFile* logFile = nullptr;
+ if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) {
+ NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, &logFile);
+ }
+
+ // On Android or B2G, this function will open a file named
+ // aFilename under a memory-reporting-specific folder
+ // (/data/local/tmp/memory-reports). Otherwise, it will open a
+ // file named aFilename under "NS_OS_TEMP_DIR".
+ nsresult rv =
+ nsDumpUtils::OpenTempFile(filename, &logFile, "memory-reports"_ns);
+ if (NS_FAILED(rv)) {
+ NS_IF_RELEASE(logFile);
+ return nullptr;
+ }
+
+ return dont_AddRef(logFile);
+ }
+
+ nsresult OpenLog(FileInfo* aLog) {
+ // Initially create the log in a file starting with "incomplete-".
+ // We'll move the file and strip off the "incomplete-" once the dump
+ // completes. (We do this because we don't want scripts which poll
+ // the filesystem looking for GC/CC dumps to grab a file before we're
+ // finished writing to it.)
+ nsAutoCString incomplete;
+ incomplete += "incomplete-";
+ incomplete += aLog->mPrefix;
+ MOZ_ASSERT(!aLog->mFile);
+ aLog->mFile = CreateTempFile(incomplete.get());
+ if (NS_WARN_IF(!aLog->mFile)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(!aLog->mStream);
+ nsresult rv = aLog->mFile->OpenANSIFileDesc("w", &aLog->mStream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ MozillaRegisterDebugFILE(aLog->mStream);
+ return NS_OK;
+ }
+
+ nsresult CloseLog(FileInfo* aLog, const nsAString& aCollectorKind) {
+ MOZ_ASSERT(aLog->mStream);
+ MOZ_ASSERT(aLog->mFile);
+
+ MozillaUnRegisterDebugFILE(aLog->mStream);
+ fclose(aLog->mStream);
+ aLog->mStream = nullptr;
+
+ // Strip off "incomplete-".
+ nsCOMPtr<nsIFile> logFileFinalDestination = CreateTempFile(aLog->mPrefix);
+ if (NS_WARN_IF(!logFileFinalDestination)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoString logFileFinalDestinationName;
+ logFileFinalDestination->GetLeafName(logFileFinalDestinationName);
+ if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aLog->mFile->MoveTo(/* directory */ nullptr, logFileFinalDestinationName);
+
+ // Save the file path.
+ aLog->mFile = logFileFinalDestination;
+
+ // Log to the error console.
+ nsAutoString logPath;
+ logFileFinalDestination->GetPath(logPath);
+ nsAutoString msg =
+ aCollectorKind + u" Collector log dumped to "_ns + logPath;
+
+ // We don't want any JS to run between ScanRoots and CollectWhite calls,
+ // and since ScanRoots calls this method, better to log the message
+ // asynchronously.
+ RefPtr<LogStringMessageAsync> log = new LogStringMessageAsync(msg);
+ NS_DispatchToCurrentThread(log);
+ return NS_OK;
+ }
+
+ int32_t mProcessIdentifier;
+ nsString mFilenameIdentifier;
+ FileInfo mGCLog;
+ FileInfo mCCLog;
+};
+
+NS_IMPL_ISUPPORTS(nsCycleCollectorLogSinkToFile, nsICycleCollectorLogSink)
+
+class nsCycleCollectorLogger final : public nsICycleCollectorListener {
+ ~nsCycleCollectorLogger() { ClearDescribers(); }
+
+ public:
+ nsCycleCollectorLogger()
+ : mLogSink(nsCycleCollector_createLogSink()),
+ mWantAllTraces(false),
+ mDisableLog(false),
+ mWantAfterProcessing(false),
+ mCCLog(nullptr) {}
+
+ NS_DECL_ISUPPORTS
+
+ void SetAllTraces() { mWantAllTraces = true; }
+
+ bool IsAllTraces() { return mWantAllTraces; }
+
+ NS_IMETHOD AllTraces(nsICycleCollectorListener** aListener) override {
+ SetAllTraces();
+ NS_ADDREF(*aListener = this);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetWantAllTraces(bool* aAllTraces) override {
+ *aAllTraces = mWantAllTraces;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetDisableLog(bool* aDisableLog) override {
+ *aDisableLog = mDisableLog;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetDisableLog(bool aDisableLog) override {
+ mDisableLog = aDisableLog;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetWantAfterProcessing(bool* aWantAfterProcessing) override {
+ *aWantAfterProcessing = mWantAfterProcessing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetWantAfterProcessing(bool aWantAfterProcessing) override {
+ mWantAfterProcessing = aWantAfterProcessing;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetLogSink(nsICycleCollectorLogSink** aLogSink) override {
+ NS_ADDREF(*aLogSink = mLogSink);
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetLogSink(nsICycleCollectorLogSink* aLogSink) override {
+ if (!aLogSink) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mLogSink = aLogSink;
+ return NS_OK;
+ }
+
+ nsresult Begin() {
+ nsresult rv;
+
+ mCurrentAddress.AssignLiteral("0x");
+ ClearDescribers();
+ if (mDisableLog) {
+ return NS_OK;
+ }
+
+ FILE* gcLog;
+ rv = mLogSink->Open(&gcLog, &mCCLog);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Dump the JS heap.
+ CollectorData* data = sCollectorData.get();
+ if (data && data->mContext) {
+ data->mContext->Runtime()->DumpJSHeap(gcLog);
+ }
+ rv = mLogSink->CloseGCLog();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fprintf(mCCLog, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false");
+ return NS_OK;
+ }
+ void NoteRefCountedObject(uint64_t aAddress, uint32_t aRefCount,
+ const char* aObjectDescription) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "%p [rc=%u] %s\n", (void*)aAddress, aRefCount,
+ aObjectDescription);
+ }
+ if (mWantAfterProcessing) {
+ CCGraphDescriber* d = new CCGraphDescriber();
+ mDescribers.insertBack(d);
+ mCurrentAddress.AssignLiteral("0x");
+ mCurrentAddress.AppendInt(aAddress, 16);
+ d->mType = CCGraphDescriber::eRefCountedObject;
+ d->mAddress = mCurrentAddress;
+ d->mCnt = aRefCount;
+ d->mName.Append(aObjectDescription);
+ }
+ }
+ void NoteGCedObject(uint64_t aAddress, bool aMarked,
+ const char* aObjectDescription,
+ uint64_t aCompartmentAddress) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "%p [gc%s] %s\n", (void*)aAddress,
+ aMarked ? ".marked" : "", aObjectDescription);
+ }
+ if (mWantAfterProcessing) {
+ CCGraphDescriber* d = new CCGraphDescriber();
+ mDescribers.insertBack(d);
+ mCurrentAddress.AssignLiteral("0x");
+ mCurrentAddress.AppendInt(aAddress, 16);
+ d->mType = aMarked ? CCGraphDescriber::eGCMarkedObject
+ : CCGraphDescriber::eGCedObject;
+ d->mAddress = mCurrentAddress;
+ d->mName.Append(aObjectDescription);
+ if (aCompartmentAddress) {
+ d->mCompartmentOrToAddress.AssignLiteral("0x");
+ d->mCompartmentOrToAddress.AppendInt(aCompartmentAddress, 16);
+ } else {
+ d->mCompartmentOrToAddress.SetIsVoid(true);
+ }
+ }
+ }
+ void NoteEdge(uint64_t aToAddress, const char* aEdgeName) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "> %p %s\n", (void*)aToAddress, aEdgeName);
+ }
+ if (mWantAfterProcessing) {
+ CCGraphDescriber* d = new CCGraphDescriber();
+ mDescribers.insertBack(d);
+ d->mType = CCGraphDescriber::eEdge;
+ d->mAddress = mCurrentAddress;
+ d->mCompartmentOrToAddress.AssignLiteral("0x");
+ d->mCompartmentOrToAddress.AppendInt(aToAddress, 16);
+ d->mName.Append(aEdgeName);
+ }
+ }
+ void NoteWeakMapEntry(uint64_t aMap, uint64_t aKey, uint64_t aKeyDelegate,
+ uint64_t aValue) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",
+ (void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue);
+ }
+ // We don't support after-processing for weak map entries.
+ }
+ void NoteIncrementalRoot(uint64_t aAddress) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "IncrementalRoot %p\n", (void*)aAddress);
+ }
+ // We don't support after-processing for incremental roots.
+ }
+ void BeginResults() {
+ if (!mDisableLog) {
+ fputs("==========\n", mCCLog);
+ }
+ }
+ void DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "%p [known=%u]\n", (void*)aAddress, aKnownEdges);
+ }
+ if (mWantAfterProcessing) {
+ CCGraphDescriber* d = new CCGraphDescriber();
+ mDescribers.insertBack(d);
+ d->mType = CCGraphDescriber::eRoot;
+ d->mAddress.AppendInt(aAddress, 16);
+ d->mCnt = aKnownEdges;
+ }
+ }
+ void DescribeGarbage(uint64_t aAddress) {
+ if (!mDisableLog) {
+ fprintf(mCCLog, "%p [garbage]\n", (void*)aAddress);
+ }
+ if (mWantAfterProcessing) {
+ CCGraphDescriber* d = new CCGraphDescriber();
+ mDescribers.insertBack(d);
+ d->mType = CCGraphDescriber::eGarbage;
+ d->mAddress.AppendInt(aAddress, 16);
+ }
+ }
+ void End() {
+ if (!mDisableLog) {
+ mCCLog = nullptr;
+ Unused << NS_WARN_IF(NS_FAILED(mLogSink->CloseCCLog()));
+ }
+ }
+ NS_IMETHOD ProcessNext(nsICycleCollectorHandler* aHandler,
+ bool* aCanContinue) override {
+ if (NS_WARN_IF(!aHandler) || NS_WARN_IF(!mWantAfterProcessing)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ CCGraphDescriber* d = mDescribers.popFirst();
+ if (d) {
+ switch (d->mType) {
+ case CCGraphDescriber::eRefCountedObject:
+ aHandler->NoteRefCountedObject(d->mAddress, d->mCnt, d->mName);
+ break;
+ case CCGraphDescriber::eGCedObject:
+ case CCGraphDescriber::eGCMarkedObject:
+ aHandler->NoteGCedObject(
+ d->mAddress, d->mType == CCGraphDescriber::eGCMarkedObject,
+ d->mName, d->mCompartmentOrToAddress);
+ break;
+ case CCGraphDescriber::eEdge:
+ aHandler->NoteEdge(d->mAddress, d->mCompartmentOrToAddress, d->mName);
+ break;
+ case CCGraphDescriber::eRoot:
+ aHandler->DescribeRoot(d->mAddress, d->mCnt);
+ break;
+ case CCGraphDescriber::eGarbage:
+ aHandler->DescribeGarbage(d->mAddress);
+ break;
+ case CCGraphDescriber::eUnknown:
+ MOZ_ASSERT_UNREACHABLE("CCGraphDescriber::eUnknown");
+ break;
+ }
+ delete d;
+ }
+ if (!(*aCanContinue = !mDescribers.isEmpty())) {
+ mCurrentAddress.AssignLiteral("0x");
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD AsLogger(nsCycleCollectorLogger** aRetVal) override {
+ RefPtr<nsCycleCollectorLogger> rval = this;
+ rval.forget(aRetVal);
+ return NS_OK;
+ }
+
+ private:
+ void ClearDescribers() {
+ CCGraphDescriber* d;
+ while ((d = mDescribers.popFirst())) {
+ delete d;
+ }
+ }
+
+ nsCOMPtr<nsICycleCollectorLogSink> mLogSink;
+ bool mWantAllTraces;
+ bool mDisableLog;
+ bool mWantAfterProcessing;
+ nsCString mCurrentAddress;
+ mozilla::LinkedList<CCGraphDescriber> mDescribers;
+ FILE* mCCLog;
+};
+
+NS_IMPL_ISUPPORTS(nsCycleCollectorLogger, nsICycleCollectorListener)
+
+already_AddRefed<nsICycleCollectorListener> nsCycleCollector_createLogger() {
+ nsCOMPtr<nsICycleCollectorListener> logger = new nsCycleCollectorLogger();
+ return logger.forget();
+}
+
+static bool GCThingIsGrayCCThing(JS::GCCellPtr thing) {
+ return JS::IsCCTraceKind(thing.kind()) && JS::GCThingIsMarkedGrayInCC(thing);
+}
+
+static bool ValueIsGrayCCThing(const JS::Value& value) {
+ return JS::IsCCTraceKind(value.traceKind()) &&
+ JS::GCThingIsMarkedGray(value.toGCCellPtr());
+}
+
+////////////////////////////////////////////////////////////////////////
+// Bacon & Rajan's |MarkRoots| routine.
+////////////////////////////////////////////////////////////////////////
+
+class CCGraphBuilder final : public nsCycleCollectionTraversalCallback,
+ public nsCycleCollectionNoteRootCallback {
+ private:
+ CCGraph& mGraph;
+ CycleCollectorResults& mResults;
+ NodePool::Builder mNodeBuilder;
+ EdgePool::Builder mEdgeBuilder;
+ MOZ_INIT_OUTSIDE_CTOR PtrInfo* mCurrPi;
+ nsCycleCollectionParticipant* mJSParticipant;
+ nsCycleCollectionParticipant* mJSZoneParticipant;
+ nsCString mNextEdgeName;
+ RefPtr<nsCycleCollectorLogger> mLogger;
+ bool mMergeZones;
+ UniquePtr<NodePool::Enumerator> mCurrNode;
+ uint32_t mNoteChildCount;
+
+ struct PtrInfoCache : public MruCache<void*, PtrInfo*, PtrInfoCache, 491> {
+ static HashNumber Hash(const void* aKey) { return HashGeneric(aKey); }
+ static bool Match(const void* aKey, const PtrInfo* aVal) {
+ return aVal->mPointer == aKey;
+ }
+ };
+
+ PtrInfoCache mGraphCache;
+
+ public:
+ CCGraphBuilder(CCGraph& aGraph, CycleCollectorResults& aResults,
+ CycleCollectedJSRuntime* aCCRuntime,
+ nsCycleCollectorLogger* aLogger, bool aMergeZones);
+ virtual ~CCGraphBuilder();
+
+ bool WantAllTraces() const {
+ return nsCycleCollectionNoteRootCallback::WantAllTraces();
+ }
+
+ bool AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti);
+
+ // This is called when all roots have been added to the graph, to prepare for
+ // BuildGraph().
+ void DoneAddingRoots();
+
+ // Do some work traversing nodes in the graph. Returns true if this graph
+ // building is finished.
+ bool BuildGraph(SliceBudget& aBudget);
+
+ void RemoveCachedEntry(void* aPtr) { mGraphCache.Remove(aPtr); }
+
+ private:
+ PtrInfo* AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant);
+ PtrInfo* AddWeakMapNode(JS::GCCellPtr aThing);
+ PtrInfo* AddWeakMapNode(JSObject* aObject);
+
+ void SetFirstChild() { mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); }
+
+ void SetLastChild() { mCurrPi->SetLastChild(mEdgeBuilder.Mark()); }
+
+ public:
+ // nsCycleCollectionNoteRootCallback methods.
+ NS_IMETHOD_(void)
+ NoteXPCOMRoot(nsISupports* aRoot,
+ nsCycleCollectionParticipant* aParticipant) override;
+ NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) override;
+ NS_IMETHOD_(void)
+ NoteNativeRoot(void* aRoot,
+ nsCycleCollectionParticipant* aParticipant) override;
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKdelegate,
+ JS::GCCellPtr aVal) override;
+ // This is used to create synthetic non-refcounted references to
+ // nsXPCWrappedJS from their wrapped JS objects. No map is needed, because
+ // the SubjectToFinalization list is like a known-black weak map, and
+ // no delegate is needed because the keys are all unwrapped objects.
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aKey, nsISupports* aVal,
+ nsCycleCollectionParticipant* aValParticipant) override;
+
+ // nsCycleCollectionTraversalCallback methods.
+ NS_IMETHOD_(void)
+ DescribeRefCountedNode(nsrefcnt aRefCount, const char* aObjName) override;
+ NS_IMETHOD_(void)
+ DescribeGCedNode(bool aIsMarked, const char* aObjName,
+ uint64_t aCompartmentAddress) override;
+
+ NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override;
+ NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override;
+ NS_IMETHOD_(void)
+ NoteNativeChild(void* aChild,
+ nsCycleCollectionParticipant* aParticipant) override;
+ NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override;
+
+ private:
+ NS_IMETHOD_(void)
+ NoteRoot(void* aRoot, nsCycleCollectionParticipant* aParticipant) {
+ MOZ_ASSERT(aRoot);
+ MOZ_ASSERT(aParticipant);
+
+ if (!aParticipant->CanSkipInCC(aRoot) || MOZ_UNLIKELY(WantAllTraces())) {
+ AddNode(aRoot, aParticipant);
+ }
+ }
+
+ NS_IMETHOD_(void)
+ NoteChild(void* aChild, nsCycleCollectionParticipant* aCp,
+ nsCString& aEdgeName) {
+ PtrInfo* childPi = AddNode(aChild, aCp);
+ if (!childPi) {
+ return;
+ }
+ mEdgeBuilder.Add(childPi);
+ if (mLogger) {
+ mLogger->NoteEdge((uint64_t)aChild, aEdgeName.get());
+ }
+ ++childPi->mInternalRefs;
+ }
+
+ JS::Zone* MergeZone(JS::GCCellPtr aGcthing) {
+ if (!mMergeZones) {
+ return nullptr;
+ }
+ JS::Zone* zone = JS::GetTenuredGCThingZone(aGcthing);
+ if (js::IsSystemZone(zone)) {
+ return nullptr;
+ }
+ return zone;
+ }
+};
+
+CCGraphBuilder::CCGraphBuilder(CCGraph& aGraph, CycleCollectorResults& aResults,
+ CycleCollectedJSRuntime* aCCRuntime,
+ nsCycleCollectorLogger* aLogger,
+ bool aMergeZones)
+ : mGraph(aGraph),
+ mResults(aResults),
+ mNodeBuilder(aGraph.mNodes),
+ mEdgeBuilder(aGraph.mEdges),
+ mJSParticipant(nullptr),
+ mJSZoneParticipant(nullptr),
+ mLogger(aLogger),
+ mMergeZones(aMergeZones),
+ mNoteChildCount(0) {
+ // 4096 is an allocation bucket size.
+ static_assert(sizeof(CCGraphBuilder) <= 4096,
+ "Don't create too large CCGraphBuilder objects");
+
+ if (aCCRuntime) {
+ mJSParticipant = aCCRuntime->GCThingParticipant();
+ mJSZoneParticipant = aCCRuntime->ZoneParticipant();
+ }
+
+ if (mLogger) {
+ mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO;
+ if (mLogger->IsAllTraces()) {
+ mFlags |= nsCycleCollectionTraversalCallback::WANT_ALL_TRACES;
+ mWantAllTraces = true; // for nsCycleCollectionNoteRootCallback
+ }
+ }
+
+ mMergeZones = mMergeZones && MOZ_LIKELY(!WantAllTraces());
+
+ MOZ_ASSERT(nsCycleCollectionNoteRootCallback::WantAllTraces() ==
+ nsCycleCollectionTraversalCallback::WantAllTraces());
+}
+
+CCGraphBuilder::~CCGraphBuilder() = default;
+
+PtrInfo* CCGraphBuilder::AddNode(void* aPtr,
+ nsCycleCollectionParticipant* aParticipant) {
+ if (mGraph.mOutOfMemory) {
+ return nullptr;
+ }
+
+ PtrInfoCache::Entry cached = mGraphCache.Lookup(aPtr);
+ if (cached) {
+#ifdef DEBUG
+ if (cached.Data()->mParticipant != aParticipant) {
+ auto* parti1 = cached.Data()->mParticipant;
+ auto* parti2 = aParticipant;
+ NS_WARNING(
+ nsPrintfCString("cached participant: %s; AddNode participant: %s\n",
+ parti1 ? parti1->ClassName() : "null",
+ parti2 ? parti2->ClassName() : "null")
+ .get());
+ }
+#endif
+ MOZ_ASSERT(cached.Data()->mParticipant == aParticipant,
+ "nsCycleCollectionParticipant shouldn't change!");
+ return cached.Data();
+ }
+
+ PtrInfo* result;
+ auto p = mGraph.mPtrInfoMap.lookupForAdd(aPtr);
+ if (!p) {
+ // New entry
+ result = mNodeBuilder.Add(aPtr, aParticipant);
+ if (!result) {
+ return nullptr;
+ }
+
+ if (!mGraph.mPtrInfoMap.add(p, result)) {
+ // `result` leaks here, but we can't free it because it's
+ // pool-allocated within NodePool.
+ mGraph.mOutOfMemory = true;
+ MOZ_ASSERT(false, "OOM while building cycle collector graph");
+ return nullptr;
+ }
+
+ } else {
+ result = *p;
+ MOZ_ASSERT(result->mParticipant == aParticipant,
+ "nsCycleCollectionParticipant shouldn't change!");
+ }
+
+ cached.Set(result);
+
+ return result;
+}
+
+bool CCGraphBuilder::AddPurpleRoot(void* aRoot,
+ nsCycleCollectionParticipant* aParti) {
+ ToParticipant(aRoot, &aParti);
+
+ if (WantAllTraces() || !aParti->CanSkipInCC(aRoot)) {
+ PtrInfo* pinfo = AddNode(aRoot, aParti);
+ if (!pinfo) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void CCGraphBuilder::DoneAddingRoots() {
+ // We've finished adding roots, and everything in the graph is a root.
+ mGraph.mRootCount = mGraph.MapCount();
+
+ mCurrNode = MakeUnique<NodePool::Enumerator>(mGraph.mNodes);
+}
+
+MOZ_NEVER_INLINE bool CCGraphBuilder::BuildGraph(SliceBudget& aBudget) {
+ MOZ_ASSERT(mCurrNode);
+
+ while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) {
+ mNoteChildCount = 0;
+
+ PtrInfo* pi = mCurrNode->GetNext();
+ if (!pi) {
+ MOZ_CRASH();
+ }
+
+ mCurrPi = pi;
+
+ // We need to call SetFirstChild() even on deleted nodes, to set their
+ // firstChild() that may be read by a prior non-deleted neighbor.
+ SetFirstChild();
+
+ if (pi->mParticipant) {
+ nsresult rv = pi->mParticipant->TraverseNativeAndJS(pi->mPointer, *this);
+ MOZ_RELEASE_ASSERT(!NS_FAILED(rv),
+ "Cycle collector Traverse method failed");
+ }
+
+ if (mCurrNode->AtBlockEnd()) {
+ SetLastChild();
+ }
+
+ aBudget.step(mNoteChildCount + 1);
+ }
+
+ if (!mCurrNode->IsDone()) {
+ return false;
+ }
+
+ if (mGraph.mRootCount > 0) {
+ SetLastChild();
+ }
+
+ mCurrNode = nullptr;
+
+ return true;
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot,
+ nsCycleCollectionParticipant* aParticipant) {
+ MOZ_ASSERT(aRoot == CanonicalizeXPCOMParticipant(aRoot));
+
+#ifdef DEBUG
+ nsXPCOMCycleCollectionParticipant* cp;
+ ToParticipant(aRoot, &cp);
+ MOZ_ASSERT(aParticipant == cp);
+#endif
+
+ NoteRoot(aRoot, aParticipant);
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteJSRoot(JSObject* aRoot) {
+ if (JS::Zone* zone = MergeZone(JS::GCCellPtr(aRoot))) {
+ NoteRoot(zone, mJSZoneParticipant);
+ } else {
+ NoteRoot(aRoot, mJSParticipant);
+ }
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteNativeRoot(void* aRoot,
+ nsCycleCollectionParticipant* aParticipant) {
+ NoteRoot(aRoot, aParticipant);
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::DescribeRefCountedNode(nsrefcnt aRefCount,
+ const char* aObjName) {
+ mCurrPi->AnnotatedReleaseAssert(aRefCount != 0,
+ "CCed refcounted object has zero refcount");
+ mCurrPi->AnnotatedReleaseAssert(
+ aRefCount != UINT32_MAX,
+ "CCed refcounted object has overflowing refcount");
+
+ mResults.mVisitedRefCounted++;
+
+ if (mLogger) {
+ mLogger->NoteRefCountedObject((uint64_t)mCurrPi->mPointer, aRefCount,
+ aObjName);
+ }
+
+ mCurrPi->mRefCount = aRefCount;
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::DescribeGCedNode(bool aIsMarked, const char* aObjName,
+ uint64_t aCompartmentAddress) {
+ uint32_t refCount = aIsMarked ? UINT32_MAX : 0;
+ mResults.mVisitedGCed++;
+
+ if (mLogger) {
+ mLogger->NoteGCedObject((uint64_t)mCurrPi->mPointer, aIsMarked, aObjName,
+ aCompartmentAddress);
+ }
+
+ mCurrPi->mRefCount = refCount;
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteXPCOMChild(nsISupports* aChild) {
+ nsCString edgeName;
+ if (WantDebugInfo()) {
+ edgeName.Assign(mNextEdgeName);
+ mNextEdgeName.Truncate();
+ }
+ if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) {
+ return;
+ }
+
+ ++mNoteChildCount;
+
+ nsXPCOMCycleCollectionParticipant* cp;
+ ToParticipant(aChild, &cp);
+ if (cp && (!cp->CanSkipThis(aChild) || WantAllTraces())) {
+ NoteChild(aChild, cp, edgeName);
+ }
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteNativeChild(void* aChild,
+ nsCycleCollectionParticipant* aParticipant) {
+ nsCString edgeName;
+ if (WantDebugInfo()) {
+ edgeName.Assign(mNextEdgeName);
+ mNextEdgeName.Truncate();
+ }
+ if (!aChild) {
+ return;
+ }
+
+ ++mNoteChildCount;
+
+ MOZ_ASSERT(aParticipant, "Need a nsCycleCollectionParticipant!");
+ if (!aParticipant->CanSkipThis(aChild) || WantAllTraces()) {
+ NoteChild(aChild, aParticipant, edgeName);
+ }
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteJSChild(JS::GCCellPtr aChild) {
+ if (!aChild) {
+ return;
+ }
+
+ ++mNoteChildCount;
+
+ nsCString edgeName;
+ if (MOZ_UNLIKELY(WantDebugInfo())) {
+ edgeName.Assign(mNextEdgeName);
+ mNextEdgeName.Truncate();
+ }
+
+ if (GCThingIsGrayCCThing(aChild) || MOZ_UNLIKELY(WantAllTraces())) {
+ if (JS::Zone* zone = MergeZone(aChild)) {
+ NoteChild(zone, mJSZoneParticipant, edgeName);
+ } else {
+ NoteChild(aChild.asCell(), mJSParticipant, edgeName);
+ }
+ }
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteNextEdgeName(const char* aName) {
+ if (WantDebugInfo()) {
+ mNextEdgeName = aName;
+ }
+}
+
+PtrInfo* CCGraphBuilder::AddWeakMapNode(JS::GCCellPtr aNode) {
+ MOZ_ASSERT(aNode, "Weak map node should be non-null.");
+
+ if (!GCThingIsGrayCCThing(aNode) && !WantAllTraces()) {
+ return nullptr;
+ }
+
+ if (JS::Zone* zone = MergeZone(aNode)) {
+ return AddNode(zone, mJSZoneParticipant);
+ }
+ return AddNode(aNode.asCell(), mJSParticipant);
+}
+
+PtrInfo* CCGraphBuilder::AddWeakMapNode(JSObject* aObject) {
+ return AddWeakMapNode(JS::GCCellPtr(aObject));
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey,
+ JSObject* aKdelegate, JS::GCCellPtr aVal) {
+ // Don't try to optimize away the entry here, as we've already attempted to
+ // do that in TraceWeakMapping in nsXPConnect.
+ WeakMapping* mapping = mGraph.mWeakMaps.AppendElement();
+ mapping->mMap = aMap ? AddWeakMapNode(aMap) : nullptr;
+ mapping->mKey = aKey ? AddWeakMapNode(aKey) : nullptr;
+ mapping->mKeyDelegate =
+ aKdelegate ? AddWeakMapNode(aKdelegate) : mapping->mKey;
+ mapping->mVal = aVal ? AddWeakMapNode(aVal) : nullptr;
+
+ if (mLogger) {
+ mLogger->NoteWeakMapEntry((uint64_t)aMap, aKey ? aKey.unsafeAsInteger() : 0,
+ (uint64_t)aKdelegate,
+ aVal ? aVal.unsafeAsInteger() : 0);
+ }
+}
+
+NS_IMETHODIMP_(void)
+CCGraphBuilder::NoteWeakMapping(JSObject* aKey, nsISupports* aVal,
+ nsCycleCollectionParticipant* aValParticipant) {
+ MOZ_ASSERT(aKey, "Don't call NoteWeakMapping with a null key");
+ MOZ_ASSERT(aVal, "Don't call NoteWeakMapping with a null value");
+ WeakMapping* mapping = mGraph.mWeakMaps.AppendElement();
+ mapping->mMap = nullptr;
+ mapping->mKey = AddWeakMapNode(aKey);
+ mapping->mKeyDelegate = mapping->mKey;
+ MOZ_ASSERT(js::UncheckedUnwrapWithoutExpose(aKey) == aKey);
+ mapping->mVal = AddNode(aVal, aValParticipant);
+
+ if (mLogger) {
+ mLogger->NoteWeakMapEntry(0, (uint64_t)aKey, 0, (uint64_t)aVal);
+ }
+}
+
+static bool AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot,
+ nsCycleCollectionParticipant* aParti) {
+ return aBuilder.AddPurpleRoot(aRoot, aParti);
+}
+
+// MayHaveChild() will be false after a Traverse if the object does
+// not have any children the CC will visit.
+class ChildFinder : public nsCycleCollectionTraversalCallback {
+ public:
+ ChildFinder() : mMayHaveChild(false) {}
+
+ // The logic of the Note*Child functions must mirror that of their
+ // respective functions in CCGraphBuilder.
+ NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override;
+ NS_IMETHOD_(void)
+ NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) override;
+ NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override;
+
+ NS_IMETHOD_(void)
+ NoteWeakMapping(JSObject* aKey, nsISupports* aVal,
+ nsCycleCollectionParticipant* aValParticipant) override {}
+
+ NS_IMETHOD_(void)
+ DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjname) override {}
+ NS_IMETHOD_(void)
+ DescribeGCedNode(bool aIsMarked, const char* aObjname,
+ uint64_t aCompartmentAddress) override {}
+ NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override {}
+ bool MayHaveChild() { return mMayHaveChild; }
+
+ private:
+ bool mMayHaveChild;
+};
+
+NS_IMETHODIMP_(void)
+ChildFinder::NoteXPCOMChild(nsISupports* aChild) {
+ if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) {
+ return;
+ }
+ nsXPCOMCycleCollectionParticipant* cp;
+ ToParticipant(aChild, &cp);
+ if (cp && !cp->CanSkip(aChild, true)) {
+ mMayHaveChild = true;
+ }
+}
+
+NS_IMETHODIMP_(void)
+ChildFinder::NoteNativeChild(void* aChild,
+ nsCycleCollectionParticipant* aHelper) {
+ if (!aChild) {
+ return;
+ }
+ MOZ_ASSERT(aHelper, "Native child must have a participant");
+ if (!aHelper->CanSkip(aChild, true)) {
+ mMayHaveChild = true;
+ }
+}
+
+NS_IMETHODIMP_(void)
+ChildFinder::NoteJSChild(JS::GCCellPtr aChild) {
+ if (aChild && JS::GCThingIsMarkedGray(aChild)) {
+ mMayHaveChild = true;
+ }
+}
+
+static bool MayHaveChild(void* aObj, nsCycleCollectionParticipant* aCp) {
+ ChildFinder cf;
+ aCp->TraverseNativeAndJS(aObj, cf);
+ return cf.MayHaveChild();
+}
+
+// JSPurpleBuffer keeps references to GCThings which might affect the
+// next cycle collection. It is owned only by itself and during unlink its
+// self reference is broken down and the object ends up killing itself.
+// If GC happens before CC, references to GCthings and the self reference are
+// removed.
+class JSPurpleBuffer {
+ ~JSPurpleBuffer() {
+ MOZ_ASSERT(mValues.IsEmpty());
+ MOZ_ASSERT(mObjects.IsEmpty());
+ }
+
+ public:
+ explicit JSPurpleBuffer(RefPtr<JSPurpleBuffer>& aReferenceToThis)
+ : mReferenceToThis(aReferenceToThis),
+ mValues(kSegmentSize),
+ mObjects(kSegmentSize) {
+ mReferenceToThis = this;
+ mozilla::HoldJSObjects(this);
+ }
+
+ void Destroy() {
+ RefPtr<JSPurpleBuffer> referenceToThis;
+ mReferenceToThis.swap(referenceToThis);
+ mValues.Clear();
+ mObjects.Clear();
+ mozilla::DropJSObjects(this);
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer)
+
+ RefPtr<JSPurpleBuffer>& mReferenceToThis;
+
+ // These are raw pointers instead of Heap<T> because we only need Heap<T> for
+ // pointers which may point into the nursery. The purple buffer never contains
+ // pointers to the nursery because nursery gcthings can never be gray and only
+ // gray things can be inserted into the purple buffer.
+ static const size_t kSegmentSize = 512;
+ SegmentedVector<JS::Value, kSegmentSize, InfallibleAllocPolicy> mValues;
+ SegmentedVector<JSObject*, kSegmentSize, InfallibleAllocPolicy> mObjects;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(JSPurpleBuffer)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSPurpleBuffer)
+ tmp->Destroy();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSPurpleBuffer)
+ CycleCollectionNoteChild(cb, tmp, "self");
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_TRACE_SEGMENTED_ARRAY(_field, _type) \
+ { \
+ for (auto iter = tmp->_field.Iter(); !iter.Done(); iter.Next()) { \
+ js::gc::CallTraceCallbackOnNonHeap<_type, TraceCallbacks>( \
+ &iter.Get(), aCallbacks, #_field, aClosure); \
+ } \
+ }
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSPurpleBuffer)
+ NS_TRACE_SEGMENTED_ARRAY(mValues, JS::Value)
+ NS_TRACE_SEGMENTED_ARRAY(mObjects, JSObject*)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+class SnowWhiteKiller : public TraceCallbacks {
+ struct SnowWhiteObject {
+ void* mPointer;
+ nsCycleCollectionParticipant* mParticipant;
+ nsCycleCollectingAutoRefCnt* mRefCnt;
+ };
+
+ // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit.
+ static const size_t kSegmentSize = sizeof(void*) * 1024;
+ typedef SegmentedVector<SnowWhiteObject, kSegmentSize, InfallibleAllocPolicy>
+ ObjectsVector;
+
+ public:
+ SnowWhiteKiller(nsCycleCollector* aCollector, js::SliceBudget* aBudget)
+ : mCollector(aCollector),
+ mObjects(kSegmentSize),
+ mBudget(aBudget),
+ mSawSnowWhiteObjects(false) {
+ MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away");
+ }
+
+ explicit SnowWhiteKiller(nsCycleCollector* aCollector)
+ : SnowWhiteKiller(aCollector, nullptr) {}
+
+ ~SnowWhiteKiller() {
+ for (auto iter = mObjects.Iter(); !iter.Done(); iter.Next()) {
+ SnowWhiteObject& o = iter.Get();
+ MaybeKillObject(o);
+ }
+ }
+
+ private:
+ void MaybeKillObject(SnowWhiteObject& aObject) {
+ if (!aObject.mRefCnt->get() && !aObject.mRefCnt->IsInPurpleBuffer()) {
+ mCollector->RemoveObjectFromGraph(aObject.mPointer);
+ aObject.mRefCnt->stabilizeForDeletion();
+ {
+ JS::AutoEnterCycleCollection autocc(mCollector->Runtime()->Runtime());
+ aObject.mParticipant->Trace(aObject.mPointer, *this, nullptr);
+ }
+ aObject.mParticipant->DeleteCycleCollectable(aObject.mPointer);
+ }
+ }
+
+ public:
+ bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) {
+ if (mBudget) {
+ if (mBudget->isOverBudget()) {
+ return false;
+ }
+ mBudget->step();
+ }
+
+ MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer");
+ if (!aEntry->mRefCnt->get()) {
+ mSawSnowWhiteObjects = true;
+ void* o = aEntry->mObject;
+ nsCycleCollectionParticipant* cp = aEntry->mParticipant;
+ ToParticipant(o, &cp);
+ SnowWhiteObject swo = {o, cp, aEntry->mRefCnt};
+ if (!mBudget) {
+ mObjects.InfallibleAppend(swo);
+ }
+ aBuffer.Remove(aEntry);
+ if (mBudget) {
+ MaybeKillObject(swo);
+ }
+ }
+ return true;
+ }
+
+ bool HasSnowWhiteObjects() const { return !mObjects.IsEmpty(); }
+
+ bool SawSnowWhiteObjects() const { return mSawSnowWhiteObjects; }
+
+ virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName,
+ void* aClosure) const override {
+ const JS::Value& val = aValue->unbarrieredGet();
+ if (val.isGCThing() && ValueIsGrayCCThing(val)) {
+ MOZ_ASSERT(!js::gc::IsInsideNursery(val.toGCThing()));
+ mCollector->GetJSPurpleBuffer()->mValues.InfallibleAppend(val);
+ }
+ }
+
+ virtual void Trace(JS::Heap<jsid>* aId, const char* aName,
+ void* aClosure) const override {}
+
+ void AppendJSObjectToPurpleBuffer(JSObject* obj) const {
+ if (obj && JS::ObjectIsMarkedGray(obj)) {
+ MOZ_ASSERT(JS::ObjectIsTenured(obj));
+ mCollector->GetJSPurpleBuffer()->mObjects.InfallibleAppend(obj);
+ }
+ }
+
+ virtual void Trace(JS::Heap<JSObject*>* aObject, const char* aName,
+ void* aClosure) const override {
+ AppendJSObjectToPurpleBuffer(aObject->unbarrieredGet());
+ }
+
+ virtual void Trace(nsWrapperCache* aWrapperCache, const char* aName,
+ void* aClosure) const override {
+ AppendJSObjectToPurpleBuffer(aWrapperCache->GetWrapperPreserveColor());
+ }
+
+ virtual void Trace(JS::TenuredHeap<JSObject*>* aObject, const char* aName,
+ void* aClosure) const override {
+ AppendJSObjectToPurpleBuffer(aObject->unbarrieredGetPtr());
+ }
+
+ virtual void Trace(JS::Heap<JSString*>* aString, const char* aName,
+ void* aClosure) const override {}
+
+ virtual void Trace(JS::Heap<JSScript*>* aScript, const char* aName,
+ void* aClosure) const override {}
+
+ virtual void Trace(JS::Heap<JSFunction*>* aFunction, const char* aName,
+ void* aClosure) const override {}
+
+ private:
+ RefPtr<nsCycleCollector> mCollector;
+ ObjectsVector mObjects;
+ js::SliceBudget* mBudget;
+ bool mSawSnowWhiteObjects;
+};
+
+class RemoveSkippableVisitor : public SnowWhiteKiller {
+ public:
+ RemoveSkippableVisitor(nsCycleCollector* aCollector, js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes,
+ bool aAsyncSnowWhiteFreeing,
+ CC_ForgetSkippableCallback aCb)
+ : SnowWhiteKiller(aCollector),
+ mBudget(aBudget),
+ mRemoveChildlessNodes(aRemoveChildlessNodes),
+ mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing),
+ mDispatchedDeferredDeletion(false),
+ mCallback(aCb) {}
+
+ ~RemoveSkippableVisitor() {
+ // Note, we must call the callback before SnowWhiteKiller calls
+ // DeleteCycleCollectable!
+ if (mCallback) {
+ mCallback();
+ }
+ if (HasSnowWhiteObjects()) {
+ // Effectively a continuation.
+ nsCycleCollector_dispatchDeferredDeletion(true);
+ }
+ }
+
+ bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) {
+ if (mBudget.isOverBudget()) {
+ return false;
+ }
+
+ // CanSkip calls can be a bit slow, so increase the likelihood that
+ // isOverBudget actually checks whether we're over the time budget.
+ mBudget.step(5);
+ MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer");
+ if (!aEntry->mRefCnt->get()) {
+ if (!mAsyncSnowWhiteFreeing) {
+ SnowWhiteKiller::Visit(aBuffer, aEntry);
+ } else if (!mDispatchedDeferredDeletion) {
+ mDispatchedDeferredDeletion = true;
+ nsCycleCollector_dispatchDeferredDeletion(false);
+ }
+ return true;
+ }
+ void* o = aEntry->mObject;
+ nsCycleCollectionParticipant* cp = aEntry->mParticipant;
+ ToParticipant(o, &cp);
+ if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) &&
+ (!mRemoveChildlessNodes || MayHaveChild(o, cp))) {
+ return true;
+ }
+ aBuffer.Remove(aEntry);
+ return true;
+ }
+
+ private:
+ js::SliceBudget& mBudget;
+ bool mRemoveChildlessNodes;
+ bool mAsyncSnowWhiteFreeing;
+ bool mDispatchedDeferredDeletion;
+ CC_ForgetSkippableCallback mCallback;
+};
+
+void nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector,
+ js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes,
+ bool aAsyncSnowWhiteFreeing,
+ CC_ForgetSkippableCallback aCb) {
+ RemoveSkippableVisitor visitor(aCollector, aBudget, aRemoveChildlessNodes,
+ aAsyncSnowWhiteFreeing, aCb);
+ VisitEntries(visitor);
+}
+
+bool nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) {
+ CheckThreadSafety();
+
+ if (mFreeingSnowWhite) {
+ return false;
+ }
+
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite);
+
+ AutoRestore<bool> ar(mFreeingSnowWhite);
+ mFreeingSnowWhite = true;
+
+ bool hadSnowWhiteObjects = false;
+ do {
+ SnowWhiteKiller visitor(this);
+ mPurpleBuf.VisitEntries(visitor);
+ hadSnowWhiteObjects = hadSnowWhiteObjects || visitor.HasSnowWhiteObjects();
+ if (!visitor.HasSnowWhiteObjects()) {
+ break;
+ }
+ } while (aUntilNoSWInPurpleBuffer);
+ return hadSnowWhiteObjects;
+}
+
+bool nsCycleCollector::FreeSnowWhiteWithBudget(js::SliceBudget& aBudget) {
+ CheckThreadSafety();
+
+ if (mFreeingSnowWhite) {
+ return false;
+ }
+
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite);
+ AutoRestore<bool> ar(mFreeingSnowWhite);
+ mFreeingSnowWhite = true;
+
+ SnowWhiteKiller visitor(this, &aBudget);
+ mPurpleBuf.VisitEntries(visitor);
+ return visitor.SawSnowWhiteObjects();
+ ;
+}
+
+void nsCycleCollector::ForgetSkippable(js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes,
+ bool aAsyncSnowWhiteFreeing) {
+ CheckThreadSafety();
+
+ if (mFreeingSnowWhite) {
+ return;
+ }
+
+ // If we remove things from the purple buffer during graph building, we may
+ // lose track of an object that was mutated during graph building.
+ MOZ_ASSERT(IsIdle());
+
+ if (mCCJSRuntime) {
+ mCCJSRuntime->PrepareForForgetSkippable();
+ }
+ MOZ_ASSERT(
+ !mScanInProgress,
+ "Don't forget skippable or free snow-white while scan is in progress.");
+ mPurpleBuf.RemoveSkippable(this, aBudget, aRemoveChildlessNodes,
+ aAsyncSnowWhiteFreeing, mForgetSkippableCB);
+}
+
+MOZ_NEVER_INLINE void nsCycleCollector::MarkRoots(SliceBudget& aBudget) {
+ JS::AutoAssertNoGC nogc;
+ TimeLog timeLog;
+ AutoRestore<bool> ar(mScanInProgress);
+ MOZ_RELEASE_ASSERT(!mScanInProgress);
+ mScanInProgress = true;
+ MOZ_ASSERT(mIncrementalPhase == GraphBuildingPhase);
+
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_BuildGraph);
+ JS::AutoEnterCycleCollection autocc(Runtime()->Runtime());
+ bool doneBuilding = mBuilder->BuildGraph(aBudget);
+
+ if (!doneBuilding) {
+ timeLog.Checkpoint("MarkRoots()");
+ return;
+ }
+
+ mBuilder = nullptr;
+ mIncrementalPhase = ScanAndCollectWhitePhase;
+ timeLog.Checkpoint("MarkRoots()");
+}
+
+////////////////////////////////////////////////////////////////////////
+// Bacon & Rajan's |ScanRoots| routine.
+////////////////////////////////////////////////////////////////////////
+
+struct ScanBlackVisitor {
+ ScanBlackVisitor(uint32_t& aWhiteNodeCount, bool& aFailed)
+ : mWhiteNodeCount(aWhiteNodeCount), mFailed(aFailed) {}
+
+ bool ShouldVisitNode(PtrInfo const* aPi) { return aPi->mColor != black; }
+
+ MOZ_NEVER_INLINE void VisitNode(PtrInfo* aPi) {
+ if (aPi->mColor == white) {
+ --mWhiteNodeCount;
+ }
+ aPi->mColor = black;
+ }
+
+ void Failed() { mFailed = true; }
+
+ private:
+ uint32_t& mWhiteNodeCount;
+ bool& mFailed;
+};
+
+static void FloodBlackNode(uint32_t& aWhiteNodeCount, bool& aFailed,
+ PtrInfo* aPi) {
+ GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(aWhiteNodeCount, aFailed))
+ .Walk(aPi);
+ MOZ_ASSERT(aPi->mColor == black || !aPi->WasTraversed(),
+ "FloodBlackNode should make aPi black");
+}
+
+// Iterate over the WeakMaps. If we mark anything while iterating
+// over the WeakMaps, we must iterate over all of the WeakMaps again.
+void nsCycleCollector::ScanWeakMaps() {
+ bool anyChanged;
+ bool failed = false;
+ do {
+ anyChanged = false;
+ for (uint32_t i = 0; i < mGraph.mWeakMaps.Length(); i++) {
+ WeakMapping* wm = &mGraph.mWeakMaps[i];
+
+ // If any of these are null, the original object was marked black.
+ uint32_t mColor = wm->mMap ? wm->mMap->mColor : black;
+ uint32_t kColor = wm->mKey ? wm->mKey->mColor : black;
+ uint32_t kdColor = wm->mKeyDelegate ? wm->mKeyDelegate->mColor : black;
+ uint32_t vColor = wm->mVal ? wm->mVal->mColor : black;
+
+ MOZ_ASSERT(mColor != grey, "Uncolored weak map");
+ MOZ_ASSERT(kColor != grey, "Uncolored weak map key");
+ MOZ_ASSERT(kdColor != grey, "Uncolored weak map key delegate");
+ MOZ_ASSERT(vColor != grey, "Uncolored weak map value");
+
+ if (mColor == black && kColor != black && kdColor == black) {
+ FloodBlackNode(mWhiteNodeCount, failed, wm->mKey);
+ anyChanged = true;
+ }
+
+ if (mColor == black && kColor == black && vColor != black) {
+ FloodBlackNode(mWhiteNodeCount, failed, wm->mVal);
+ anyChanged = true;
+ }
+ }
+ } while (anyChanged);
+
+ if (failed) {
+ MOZ_ASSERT(false, "Ran out of memory in ScanWeakMaps");
+ CC_TELEMETRY(_OOM, true);
+ }
+}
+
+// Flood black from any objects in the purple buffer that are in the CC graph.
+class PurpleScanBlackVisitor {
+ public:
+ PurpleScanBlackVisitor(CCGraph& aGraph, nsCycleCollectorLogger* aLogger,
+ uint32_t& aCount, bool& aFailed)
+ : mGraph(aGraph), mLogger(aLogger), mCount(aCount), mFailed(aFailed) {}
+
+ bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) {
+ MOZ_ASSERT(aEntry->mObject,
+ "Entries with null mObject shouldn't be in the purple buffer.");
+ MOZ_ASSERT(aEntry->mRefCnt->get() != 0,
+ "Snow-white objects shouldn't be in the purple buffer.");
+
+ void* obj = aEntry->mObject;
+
+ MOZ_ASSERT(
+ aEntry->mParticipant ||
+ CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj)) == obj,
+ "Suspect nsISupports pointer must be canonical");
+
+ PtrInfo* pi = mGraph.FindNode(obj);
+ if (!pi) {
+ return true;
+ }
+ MOZ_ASSERT(pi->mParticipant,
+ "No dead objects should be in the purple buffer.");
+ if (MOZ_UNLIKELY(mLogger)) {
+ mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer);
+ }
+ if (pi->mColor == black) {
+ return true;
+ }
+ FloodBlackNode(mCount, mFailed, pi);
+ return true;
+ }
+
+ private:
+ CCGraph& mGraph;
+ RefPtr<nsCycleCollectorLogger> mLogger;
+ uint32_t& mCount;
+ bool& mFailed;
+};
+
+// Objects that have been stored somewhere since the start of incremental graph
+// building must be treated as live for this cycle collection, because we may
+// not have accurate information about who holds references to them.
+void nsCycleCollector::ScanIncrementalRoots() {
+ TimeLog timeLog;
+
+ // Reference counted objects:
+ // We cleared the purple buffer at the start of the current ICC, so if a
+ // refcounted object is purple, it may have been AddRef'd during the current
+ // ICC. (It may also have only been released.) If that is the case, we cannot
+ // be sure that the set of things pointing to the object in the CC graph
+ // is accurate. Therefore, for safety, we treat any purple objects as being
+ // live during the current CC. We don't remove anything from the purple
+ // buffer here, so these objects will be suspected and freed in the next CC
+ // if they are garbage.
+ bool failed = false;
+ PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mLogger,
+ mWhiteNodeCount, failed);
+ mPurpleBuf.VisitEntries(purpleScanBlackVisitor);
+ timeLog.Checkpoint("ScanIncrementalRoots::fix purple");
+
+ bool hasJSRuntime = !!mCCJSRuntime;
+ nsCycleCollectionParticipant* jsParticipant =
+ hasJSRuntime ? mCCJSRuntime->GCThingParticipant() : nullptr;
+ nsCycleCollectionParticipant* zoneParticipant =
+ hasJSRuntime ? mCCJSRuntime->ZoneParticipant() : nullptr;
+ bool hasLogger = !!mLogger;
+
+ NodePool::Enumerator etor(mGraph.mNodes);
+ while (!etor.IsDone()) {
+ PtrInfo* pi = etor.GetNext();
+
+ // As an optimization, if an object has already been determined to be live,
+ // don't consider it further. We can't do this if there is a listener,
+ // because the listener wants to know the complete set of incremental roots.
+ if (pi->mColor == black && MOZ_LIKELY(!hasLogger)) {
+ continue;
+ }
+
+ // Garbage collected objects:
+ // If a GCed object was added to the graph with a refcount of zero, and is
+ // now marked black by the GC, it was probably gray before and was exposed
+ // to active JS, so it may have been stored somewhere, so it needs to be
+ // treated as live.
+ if (pi->IsGrayJS() && MOZ_LIKELY(hasJSRuntime)) {
+ // If the object is still marked gray by the GC, nothing could have gotten
+ // hold of it, so it isn't an incremental root.
+ if (pi->mParticipant == jsParticipant) {
+ JS::GCCellPtr ptr(pi->mPointer, JS::GCThingTraceKind(pi->mPointer));
+ if (GCThingIsGrayCCThing(ptr)) {
+ continue;
+ }
+ } else if (pi->mParticipant == zoneParticipant) {
+ JS::Zone* zone = static_cast<JS::Zone*>(pi->mPointer);
+ if (js::ZoneGlobalsAreAllGray(zone)) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live.");
+ }
+ } else if (!pi->mParticipant && pi->WasTraversed()) {
+ // Dead traversed refcounted objects:
+ // If the object was traversed, it must have been alive at the start of
+ // the CC, and thus had a positive refcount. It is dead now, so its
+ // refcount must have decreased at some point during the CC. Therefore,
+ // it would be in the purple buffer if it wasn't dead, so treat it as an
+ // incremental root.
+ //
+ // This should not cause leaks because as the object died it should have
+ // released anything it held onto, which will add them to the purple
+ // buffer, which will cause them to be considered in the next CC.
+ } else {
+ continue;
+ }
+
+ // At this point, pi must be an incremental root.
+
+ // If there's a listener, tell it about this root. We don't bother with the
+ // optimization of skipping the Walk() if pi is black: it will just return
+ // without doing anything and there's no need to make this case faster.
+ if (MOZ_UNLIKELY(hasLogger) && pi->mPointer) {
+ // Dead objects aren't logged. See bug 1031370.
+ mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer);
+ }
+
+ FloodBlackNode(mWhiteNodeCount, failed, pi);
+ }
+
+ timeLog.Checkpoint("ScanIncrementalRoots::fix nodes");
+
+ if (failed) {
+ NS_ASSERTION(false, "Ran out of memory in ScanIncrementalRoots");
+ CC_TELEMETRY(_OOM, true);
+ }
+}
+
+// Mark nodes white and make sure their refcounts are ok.
+// No nodes are marked black during this pass to ensure that refcount
+// checking is run on all nodes not marked black by ScanIncrementalRoots.
+void nsCycleCollector::ScanWhiteNodes(bool aFullySynchGraphBuild) {
+ NodePool::Enumerator nodeEnum(mGraph.mNodes);
+ while (!nodeEnum.IsDone()) {
+ PtrInfo* pi = nodeEnum.GetNext();
+ if (pi->mColor == black) {
+ // Incremental roots can be in a nonsensical state, so don't
+ // check them. This will miss checking nodes that are merely
+ // reachable from incremental roots.
+ MOZ_ASSERT(!aFullySynchGraphBuild,
+ "In a synch CC, no nodes should be marked black early on.");
+ continue;
+ }
+ MOZ_ASSERT(pi->mColor == grey);
+
+ if (!pi->WasTraversed()) {
+ // This node was deleted before it was traversed, so there's no reason
+ // to look at it.
+ MOZ_ASSERT(!pi->mParticipant,
+ "Live nodes should all have been traversed");
+ continue;
+ }
+
+ if (pi->mInternalRefs == pi->mRefCount || pi->IsGrayJS()) {
+ pi->mColor = white;
+ ++mWhiteNodeCount;
+ continue;
+ }
+
+ pi->AnnotatedReleaseAssert(
+ pi->mInternalRefs <= pi->mRefCount,
+ "More references to an object than its refcount");
+
+ // This node will get marked black in the next pass.
+ }
+}
+
+// Any remaining grey nodes that haven't already been deleted must be alive,
+// so mark them and their children black. Any nodes that are black must have
+// already had their children marked black, so there's no need to look at them
+// again. This pass may turn some white nodes to black.
+void nsCycleCollector::ScanBlackNodes() {
+ bool failed = false;
+ NodePool::Enumerator nodeEnum(mGraph.mNodes);
+ while (!nodeEnum.IsDone()) {
+ PtrInfo* pi = nodeEnum.GetNext();
+ if (pi->mColor == grey && pi->WasTraversed()) {
+ FloodBlackNode(mWhiteNodeCount, failed, pi);
+ }
+ }
+
+ if (failed) {
+ NS_ASSERTION(false, "Ran out of memory in ScanBlackNodes");
+ CC_TELEMETRY(_OOM, true);
+ }
+}
+
+void nsCycleCollector::ScanRoots(bool aFullySynchGraphBuild) {
+ JS::AutoAssertNoGC nogc;
+ AutoRestore<bool> ar(mScanInProgress);
+ MOZ_RELEASE_ASSERT(!mScanInProgress);
+ mScanInProgress = true;
+ mWhiteNodeCount = 0;
+ MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase);
+
+ JS::AutoEnterCycleCollection autocc(Runtime()->Runtime());
+
+ if (!aFullySynchGraphBuild) {
+ ScanIncrementalRoots();
+ }
+
+ TimeLog timeLog;
+ ScanWhiteNodes(aFullySynchGraphBuild);
+ timeLog.Checkpoint("ScanRoots::ScanWhiteNodes");
+
+ ScanBlackNodes();
+ timeLog.Checkpoint("ScanRoots::ScanBlackNodes");
+
+ // Scanning weak maps must be done last.
+ ScanWeakMaps();
+ timeLog.Checkpoint("ScanRoots::ScanWeakMaps");
+
+ if (mLogger) {
+ mLogger->BeginResults();
+
+ NodePool::Enumerator etor(mGraph.mNodes);
+ while (!etor.IsDone()) {
+ PtrInfo* pi = etor.GetNext();
+ if (!pi->WasTraversed()) {
+ continue;
+ }
+ switch (pi->mColor) {
+ case black:
+ if (!pi->IsGrayJS() && !pi->IsBlackJS() &&
+ pi->mInternalRefs != pi->mRefCount) {
+ mLogger->DescribeRoot((uint64_t)pi->mPointer, pi->mInternalRefs);
+ }
+ break;
+ case white:
+ mLogger->DescribeGarbage((uint64_t)pi->mPointer);
+ break;
+ case grey:
+ MOZ_ASSERT(false, "All traversed objects should be black or white");
+ break;
+ }
+ }
+
+ mLogger->End();
+ mLogger = nullptr;
+ timeLog.Checkpoint("ScanRoots::listener");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Bacon & Rajan's |CollectWhite| routine, somewhat modified.
+////////////////////////////////////////////////////////////////////////
+
+bool nsCycleCollector::CollectWhite() {
+ // Explanation of "somewhat modified": we have no way to collect the
+ // set of whites "all at once", we have to ask each of them to drop
+ // their outgoing links and assume this will cause the garbage cycle
+ // to *mostly* self-destruct (except for the reference we continue
+ // to hold).
+ //
+ // To do this "safely" we must make sure that the white nodes we're
+ // operating on are stable for the duration of our operation. So we
+ // make 3 sets of calls to language runtimes:
+ //
+ // - Root(whites), which should pin the whites in memory.
+ // - Unlink(whites), which drops outgoing links on each white.
+ // - Unroot(whites), which returns the whites to normal GC.
+
+ // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit.
+ static const size_t kSegmentSize = sizeof(void*) * 1024;
+ SegmentedVector<PtrInfo*, kSegmentSize, InfallibleAllocPolicy> whiteNodes(
+ kSegmentSize);
+ TimeLog timeLog;
+
+ MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase);
+
+ uint32_t numWhiteNodes = 0;
+ uint32_t numWhiteGCed = 0;
+ uint32_t numWhiteJSZones = 0;
+
+ {
+ JS::AutoAssertNoGC nogc;
+ bool hasJSRuntime = !!mCCJSRuntime;
+ nsCycleCollectionParticipant* zoneParticipant =
+ hasJSRuntime ? mCCJSRuntime->ZoneParticipant() : nullptr;
+
+ NodePool::Enumerator etor(mGraph.mNodes);
+ while (!etor.IsDone()) {
+ PtrInfo* pinfo = etor.GetNext();
+ if (pinfo->mColor == white && pinfo->mParticipant) {
+ if (pinfo->IsGrayJS()) {
+ MOZ_ASSERT(mCCJSRuntime);
+ ++numWhiteGCed;
+ JS::Zone* zone;
+ if (MOZ_UNLIKELY(pinfo->mParticipant == zoneParticipant)) {
+ ++numWhiteJSZones;
+ zone = static_cast<JS::Zone*>(pinfo->mPointer);
+ } else {
+ JS::GCCellPtr ptr(pinfo->mPointer,
+ JS::GCThingTraceKind(pinfo->mPointer));
+ zone = JS::GetTenuredGCThingZone(ptr);
+ }
+ mCCJSRuntime->AddZoneWaitingForGC(zone);
+ } else {
+ whiteNodes.InfallibleAppend(pinfo);
+ pinfo->mParticipant->Root(pinfo->mPointer);
+ ++numWhiteNodes;
+ }
+ }
+ }
+ }
+
+ mResults.mFreedRefCounted += numWhiteNodes;
+ mResults.mFreedGCed += numWhiteGCed;
+ mResults.mFreedJSZones += numWhiteJSZones;
+
+ timeLog.Checkpoint("CollectWhite::Root");
+
+ if (mBeforeUnlinkCB) {
+ mBeforeUnlinkCB();
+ timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB");
+ }
+
+ // Unlink() can trigger a GC, so do not touch any JS or anything
+ // else not in whiteNodes after here.
+
+ for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) {
+ PtrInfo* pinfo = iter.Get();
+ MOZ_ASSERT(pinfo->mParticipant,
+ "Unlink shouldn't see objects removed from graph.");
+ pinfo->mParticipant->Unlink(pinfo->mPointer);
+#ifdef DEBUG
+ if (mCCJSRuntime) {
+ mCCJSRuntime->AssertNoObjectsToTrace(pinfo->mPointer);
+ }
+#endif
+ }
+ timeLog.Checkpoint("CollectWhite::Unlink");
+
+ JS::AutoAssertNoGC nogc;
+ for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) {
+ PtrInfo* pinfo = iter.Get();
+ MOZ_ASSERT(pinfo->mParticipant,
+ "Unroot shouldn't see objects removed from graph.");
+ pinfo->mParticipant->Unroot(pinfo->mPointer);
+ }
+ timeLog.Checkpoint("CollectWhite::Unroot");
+
+ nsCycleCollector_dispatchDeferredDeletion(false, true);
+ timeLog.Checkpoint("CollectWhite::dispatchDeferredDeletion");
+
+ mIncrementalPhase = CleanupPhase;
+
+ return numWhiteNodes > 0 || numWhiteGCed > 0 || numWhiteJSZones > 0;
+}
+
+////////////////////////
+// Memory reporting
+////////////////////////
+
+MOZ_DEFINE_MALLOC_SIZE_OF(CycleCollectorMallocSizeOf)
+
+NS_IMETHODIMP
+nsCycleCollector::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ size_t objectSize, graphSize, purpleBufferSize;
+ SizeOfIncludingThis(CycleCollectorMallocSizeOf, &objectSize, &graphSize,
+ &purpleBufferSize);
+
+ if (objectSize > 0) {
+ MOZ_COLLECT_REPORT("explicit/cycle-collector/collector-object", KIND_HEAP,
+ UNITS_BYTES, objectSize,
+ "Memory used for the cycle collector object itself.");
+ }
+
+ if (graphSize > 0) {
+ MOZ_COLLECT_REPORT(
+ "explicit/cycle-collector/graph", KIND_HEAP, UNITS_BYTES, graphSize,
+ "Memory used for the cycle collector's graph. This should be zero when "
+ "the collector is idle.");
+ }
+
+ if (purpleBufferSize > 0) {
+ MOZ_COLLECT_REPORT("explicit/cycle-collector/purple-buffer", KIND_HEAP,
+ UNITS_BYTES, purpleBufferSize,
+ "Memory used for the cycle collector's purple buffer.");
+ }
+
+ return NS_OK;
+};
+
+////////////////////////////////////////////////////////////////////////
+// Collector implementation
+////////////////////////////////////////////////////////////////////////
+
+nsCycleCollector::nsCycleCollector()
+ : mActivelyCollecting(false),
+ mFreeingSnowWhite(false),
+ mScanInProgress(false),
+ mCCJSRuntime(nullptr),
+ mIncrementalPhase(IdlePhase),
+#ifdef DEBUG
+ mEventTarget(GetCurrentSerialEventTarget()),
+#endif
+ mWhiteNodeCount(0),
+ mBeforeUnlinkCB(nullptr),
+ mForgetSkippableCB(nullptr),
+ mUnmergedNeeded(0),
+ mMergedInARow(0) {
+}
+
+nsCycleCollector::~nsCycleCollector() {
+ MOZ_ASSERT(!mJSPurpleBuffer, "Didn't call JSPurpleBuffer::Destroy?");
+
+ UnregisterWeakMemoryReporter(this);
+}
+
+void nsCycleCollector::SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime) {
+ MOZ_RELEASE_ASSERT(
+ !mCCJSRuntime,
+ "Multiple registrations of CycleCollectedJSRuntime in cycle collector");
+ mCCJSRuntime = aCCRuntime;
+
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // We can't register as a reporter in nsCycleCollector() because that runs
+ // before the memory reporter manager is initialized. So we do it here
+ // instead.
+ RegisterWeakMemoryReporter(this);
+}
+
+void nsCycleCollector::ClearCCJSRuntime() {
+ MOZ_RELEASE_ASSERT(mCCJSRuntime,
+ "Clearing CycleCollectedJSRuntime in cycle collector "
+ "before a runtime was registered");
+ mCCJSRuntime = nullptr;
+}
+
+#ifdef DEBUG
+static bool HasParticipant(void* aPtr, nsCycleCollectionParticipant* aParti) {
+ if (aParti) {
+ return true;
+ }
+
+ nsXPCOMCycleCollectionParticipant* xcp;
+ ToParticipant(static_cast<nsISupports*>(aPtr), &xcp);
+ return xcp != nullptr;
+}
+#endif
+
+MOZ_ALWAYS_INLINE void nsCycleCollector::Suspect(
+ void* aPtr, nsCycleCollectionParticipant* aParti,
+ nsCycleCollectingAutoRefCnt* aRefCnt) {
+ CheckThreadSafety();
+
+ // Don't call AddRef or Release of a CCed object in a Traverse() method.
+ MOZ_ASSERT(!mScanInProgress,
+ "Attempted to call Suspect() while a scan was in progress");
+
+ if (MOZ_UNLIKELY(mScanInProgress)) {
+ return;
+ }
+
+ MOZ_ASSERT(aPtr, "Don't suspect null pointers");
+
+ MOZ_ASSERT(HasParticipant(aPtr, aParti),
+ "Suspected nsISupports pointer must QI to "
+ "nsXPCOMCycleCollectionParticipant");
+
+ MOZ_ASSERT(aParti || CanonicalizeXPCOMParticipant(
+ static_cast<nsISupports*>(aPtr)) == aPtr,
+ "Suspect nsISupports pointer must be canonical");
+
+ mPurpleBuf.Put(aPtr, aParti, aRefCnt);
+}
+
+void nsCycleCollector::SuspectNurseryEntries() {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ while (gNurseryPurpleBufferEntryCount) {
+ NurseryPurpleBufferEntry& entry =
+ gNurseryPurpleBufferEntry[--gNurseryPurpleBufferEntryCount];
+ mPurpleBuf.Put(entry.mPtr, entry.mParticipant, entry.mRefCnt);
+ }
+}
+
+void nsCycleCollector::CheckThreadSafety() {
+#ifdef DEBUG
+ MOZ_ASSERT(mEventTarget->IsOnCurrentThread());
+#endif
+}
+
+// The cycle collector uses the mark bitmap to discover what JS objects are
+// reachable only from XPConnect roots that might participate in cycles. We ask
+// the JS runtime whether we need to force a GC before this CC. It should only
+// be true when UnmarkGray has run out of stack. We also force GCs on shutdown
+// to collect cycles involving both DOM and JS, and in WantAllTraces CCs to
+// prevent hijinks from ForgetSkippable and compartmental GCs.
+void nsCycleCollector::FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog) {
+ CheckThreadSafety();
+
+ if (!mCCJSRuntime) {
+ return;
+ }
+
+ // If we're not forcing a GC anyways due to shutdown or an all traces CC,
+ // check to see if we still need to do one to fix the gray bits.
+ if (!(aIsShutdown || (mLogger && mLogger->IsAllTraces()))) {
+ mCCJSRuntime->FixWeakMappingGrayBits();
+ aTimeLog.Checkpoint("FixWeakMappingGrayBits");
+
+ bool needGC = !mCCJSRuntime->AreGCGrayBitsValid();
+ // Only do a telemetry ping for non-shutdown CCs.
+ CC_TELEMETRY(_NEED_GC, needGC);
+ if (!needGC) {
+ return;
+ }
+ }
+
+ mResults.mForcedGC = true;
+
+ uint32_t count = 0;
+ do {
+ if (aIsShutdown) {
+ mCCJSRuntime->GarbageCollect(JS::GCOptions::Shutdown,
+ JS::GCReason::SHUTDOWN_CC);
+ } else {
+ mCCJSRuntime->GarbageCollect(JS::GCOptions::Normal,
+ JS::GCReason::CC_FORCED);
+ }
+
+ mCCJSRuntime->FixWeakMappingGrayBits();
+
+ // It's possible that FixWeakMappingGrayBits will hit OOM when unmarking
+ // gray and we will have to go round again. The second time there should not
+ // be any weak mappings to fix up so the loop body should run at most twice.
+ MOZ_RELEASE_ASSERT(count < 2);
+ count++;
+ } while (!mCCJSRuntime->AreGCGrayBitsValid());
+
+ aTimeLog.Checkpoint("FixGrayBits");
+}
+
+bool nsCycleCollector::IsIncrementalGCInProgress() {
+ return mCCJSRuntime && JS::IsIncrementalGCInProgress(mCCJSRuntime->Runtime());
+}
+
+void nsCycleCollector::FinishAnyIncrementalGCInProgress() {
+ if (IsIncrementalGCInProgress()) {
+ NS_WARNING("Finishing incremental GC in progress during CC");
+ JSContext* cx = CycleCollectedJSContext::Get()->Context();
+ JS::PrepareForIncrementalGC(cx);
+ JS::FinishIncrementalGC(cx, JS::GCReason::CC_FORCED);
+ }
+}
+
+void nsCycleCollector::CleanupAfterCollection() {
+ TimeLog timeLog;
+ MOZ_ASSERT(mIncrementalPhase == CleanupPhase);
+ MOZ_RELEASE_ASSERT(!mScanInProgress);
+ mGraph.Clear();
+ timeLog.Checkpoint("CleanupAfterCollection::mGraph.Clear()");
+
+ uint32_t interval =
+ (uint32_t)((TimeStamp::Now() - mCollectionStart).ToMilliseconds());
+#ifdef COLLECT_TIME_DEBUG
+ printf("cc: total cycle collector time was %ums in %u slices\n", interval,
+ mResults.mNumSlices);
+ printf(
+ "cc: visited %u ref counted and %u GCed objects, freed %d ref counted "
+ "and %d GCed objects",
+ mResults.mVisitedRefCounted, mResults.mVisitedGCed,
+ mResults.mFreedRefCounted, mResults.mFreedGCed);
+ uint32_t numVisited = mResults.mVisitedRefCounted + mResults.mVisitedGCed;
+ if (numVisited > 1000) {
+ uint32_t numFreed = mResults.mFreedRefCounted + mResults.mFreedGCed;
+ printf(" (%d%%)", 100 * numFreed / numVisited);
+ }
+ printf(".\ncc: \n");
+#endif
+
+ CC_TELEMETRY(, interval);
+ CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted);
+ CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed);
+ CC_TELEMETRY(_COLLECTED, mWhiteNodeCount);
+ timeLog.Checkpoint("CleanupAfterCollection::telemetry");
+
+ if (mCCJSRuntime) {
+ mCCJSRuntime->FinalizeDeferredThings(
+ mResults.mAnyManual ? CycleCollectedJSRuntime::FinalizeNow
+ : CycleCollectedJSRuntime::FinalizeIncrementally);
+ mCCJSRuntime->EndCycleCollectionCallback(mResults);
+ timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()");
+ }
+ mIncrementalPhase = IdlePhase;
+}
+
+void nsCycleCollector::ShutdownCollect() {
+ FinishAnyIncrementalGCInProgress();
+ CycleCollectedJSContext* ccJSContext = CycleCollectedJSContext::Get();
+ JS::ShutdownAsyncTasks(ccJSContext->Context());
+
+ SliceBudget unlimitedBudget = SliceBudget::unlimited();
+ uint32_t i;
+ bool collectedAny = true;
+ for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS && collectedAny; ++i) {
+ collectedAny = Collect(CCReason::SHUTDOWN, ccIsManual::CCIsManual,
+ unlimitedBudget, nullptr);
+ // Run any remaining tasks that may have been enqueued via RunInStableState
+ // or DispatchToMicroTask. These can hold alive CCed objects, and we want to
+ // clear them out before we run the CC again or finish shutting down.
+ ccJSContext->PerformMicroTaskCheckPoint(true);
+ ccJSContext->ProcessStableStateQueue();
+ }
+
+ // This warning would happen very frequently, so don't do it unless we're
+ // logging this CC, so we might care about how many CCs there are.
+ NS_WARNING_ASSERTION(
+ !mParams.LogThisCC(mShutdownCount) || i < NORMAL_SHUTDOWN_COLLECTIONS,
+ "Extra shutdown CC");
+}
+
+static void PrintPhase(const char* aPhase) {
+#ifdef DEBUG_PHASES
+ printf("cc: begin %s on %s\n", aPhase,
+ NS_IsMainThread() ? "mainthread" : "worker");
+#endif
+}
+
+bool nsCycleCollector::Collect(CCReason aReason, ccIsManual aIsManual,
+ SliceBudget& aBudget,
+ nsICycleCollectorListener* aManualListener,
+ bool aPreferShorterSlices) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC);
+
+ CheckThreadSafety();
+
+ // This can legitimately happen in a few cases. See bug 383651.
+ if (mActivelyCollecting || mFreeingSnowWhite) {
+ return false;
+ }
+ mActivelyCollecting = true;
+
+ MOZ_ASSERT(!IsIncrementalGCInProgress());
+
+ bool startedIdle = IsIdle();
+ bool collectedAny = false;
+
+ // If the CC started idle, it will call BeginCollection, which
+ // will do FreeSnowWhite, so it doesn't need to be done here.
+ if (!startedIdle) {
+ TimeLog timeLog;
+ FreeSnowWhite(true);
+ timeLog.Checkpoint("Collect::FreeSnowWhite");
+ }
+
+ if (aIsManual == ccIsManual::CCIsManual) {
+ mResults.mAnyManual = true;
+ }
+
+ ++mResults.mNumSlices;
+
+ bool continueSlice = aBudget.isUnlimited() || !aPreferShorterSlices;
+ do {
+ switch (mIncrementalPhase) {
+ case IdlePhase:
+ PrintPhase("BeginCollection");
+ BeginCollection(aReason, aIsManual, aManualListener);
+ break;
+ case GraphBuildingPhase:
+ PrintPhase("MarkRoots");
+ MarkRoots(aBudget);
+
+ // Only continue this slice if we're running synchronously or the
+ // next phase will probably be short, to reduce the max pause for this
+ // collection.
+ // (There's no need to check if we've finished graph building, because
+ // if we haven't, we've already exceeded our budget, and will finish
+ // this slice anyways.)
+ continueSlice = aBudget.isUnlimited() ||
+ (mResults.mNumSlices < 3 && !aPreferShorterSlices);
+ break;
+ case ScanAndCollectWhitePhase:
+ // We do ScanRoots and CollectWhite in a single slice to ensure
+ // that we won't unlink a live object if a weak reference is
+ // promoted to a strong reference after ScanRoots has finished.
+ // See bug 926533.
+ {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_ScanRoots);
+ PrintPhase("ScanRoots");
+ ScanRoots(startedIdle);
+ }
+ {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_CollectWhite);
+ PrintPhase("CollectWhite");
+ collectedAny = CollectWhite();
+ }
+ break;
+ case CleanupPhase:
+ PrintPhase("CleanupAfterCollection");
+ CleanupAfterCollection();
+ continueSlice = false;
+ break;
+ }
+ if (continueSlice) {
+ aBudget.forceCheck();
+ continueSlice = !aBudget.isOverBudget();
+ }
+ } while (continueSlice);
+
+ // Clear mActivelyCollecting here to ensure that a recursive call to
+ // Collect() does something.
+ mActivelyCollecting = false;
+
+ if (aIsManual && !startedIdle) {
+ // We were in the middle of an incremental CC (using its own listener).
+ // Somebody has forced a CC, so after having finished out the current CC,
+ // run the CC again using the new listener.
+ MOZ_ASSERT(IsIdle());
+ if (Collect(aReason, ccIsManual::CCIsManual, aBudget, aManualListener)) {
+ collectedAny = true;
+ }
+ }
+
+ MOZ_ASSERT_IF(aIsManual == CCIsManual, IsIdle());
+
+ return collectedAny;
+}
+
+// Any JS objects we have in the graph could die when we GC, but we
+// don't want to abandon the current CC, because the graph contains
+// information about purple roots. So we synchronously finish off
+// the current CC.
+void nsCycleCollector::PrepareForGarbageCollection() {
+ if (IsIdle()) {
+ MOZ_ASSERT(mGraph.IsEmpty(), "Non-empty graph when idle");
+ MOZ_ASSERT(!mBuilder, "Non-null builder when idle");
+ if (mJSPurpleBuffer) {
+ mJSPurpleBuffer->Destroy();
+ }
+ return;
+ }
+
+ FinishAnyCurrentCollection(CCReason::GC_WAITING);
+}
+
+void nsCycleCollector::FinishAnyCurrentCollection(CCReason aReason) {
+ if (IsIdle()) {
+ return;
+ }
+
+ SliceBudget unlimitedBudget = SliceBudget::unlimited();
+ PrintPhase("FinishAnyCurrentCollection");
+ // Use CCIsNotManual because we only want to finish the CC in progress.
+ Collect(aReason, ccIsManual::CCIsNotManual, unlimitedBudget, nullptr);
+
+ // It is only okay for Collect() to have failed to finish the
+ // current CC if we're reentering the CC at some point past
+ // graph building. We need to be past the point where the CC will
+ // look at JS objects so that it is safe to GC.
+ MOZ_ASSERT(IsIdle() || (mActivelyCollecting &&
+ mIncrementalPhase != GraphBuildingPhase),
+ "Reentered CC during graph building");
+}
+
+// Don't merge too many times in a row, and do at least a minimum
+// number of unmerged CCs in a row.
+static const uint32_t kMinConsecutiveUnmerged = 3;
+static const uint32_t kMaxConsecutiveMerged = 3;
+
+bool nsCycleCollector::ShouldMergeZones(ccIsManual aIsManual) {
+ if (!mCCJSRuntime) {
+ return false;
+ }
+
+ MOZ_ASSERT(mUnmergedNeeded <= kMinConsecutiveUnmerged);
+ MOZ_ASSERT(mMergedInARow <= kMaxConsecutiveMerged);
+
+ if (mMergedInARow == kMaxConsecutiveMerged) {
+ MOZ_ASSERT(mUnmergedNeeded == 0);
+ mUnmergedNeeded = kMinConsecutiveUnmerged;
+ }
+
+ if (mUnmergedNeeded > 0) {
+ mUnmergedNeeded--;
+ mMergedInARow = 0;
+ return false;
+ }
+
+ if (aIsManual == CCIsNotManual && mCCJSRuntime->UsefulToMergeZones()) {
+ mMergedInARow++;
+ return true;
+ } else {
+ mMergedInARow = 0;
+ return false;
+ }
+}
+
+void nsCycleCollector::BeginCollection(
+ CCReason aReason, ccIsManual aIsManual,
+ nsICycleCollectorListener* aManualListener) {
+ TimeLog timeLog;
+ MOZ_ASSERT(IsIdle());
+ MOZ_RELEASE_ASSERT(!mScanInProgress);
+
+ mCollectionStart = TimeStamp::Now();
+
+ if (mCCJSRuntime) {
+ mCCJSRuntime->BeginCycleCollectionCallback(aReason);
+ timeLog.Checkpoint("BeginCycleCollectionCallback()");
+ }
+
+ bool isShutdown = (aReason == CCReason::SHUTDOWN);
+ if (isShutdown) {
+ mShutdownCount += 1;
+ }
+
+ // Set up the listener for this CC.
+ MOZ_ASSERT_IF(isShutdown, !aManualListener);
+ MOZ_ASSERT(!mLogger, "Forgot to clear a previous listener?");
+
+ if (aManualListener) {
+ aManualListener->AsLogger(getter_AddRefs(mLogger));
+ }
+
+ aManualListener = nullptr;
+ if (!mLogger && mParams.LogThisCC(mShutdownCount)) {
+ mLogger = new nsCycleCollectorLogger();
+ if (mParams.AllTracesThisCC(isShutdown)) {
+ mLogger->SetAllTraces();
+ }
+ }
+
+ // BeginCycleCollectionCallback() might have started an IGC, and we need
+ // to finish it before we run FixGrayBits.
+ FinishAnyIncrementalGCInProgress();
+ timeLog.Checkpoint("Pre-FixGrayBits finish IGC");
+
+ FixGrayBits(isShutdown, timeLog);
+ if (mCCJSRuntime) {
+ mCCJSRuntime->CheckGrayBits();
+ }
+
+ FreeSnowWhite(true);
+ timeLog.Checkpoint("BeginCollection FreeSnowWhite");
+
+ if (mLogger && NS_FAILED(mLogger->Begin())) {
+ mLogger = nullptr;
+ }
+
+ // FreeSnowWhite could potentially have started an IGC, which we need
+ // to finish before we look at any JS roots.
+ FinishAnyIncrementalGCInProgress();
+ timeLog.Checkpoint("Post-FreeSnowWhite finish IGC");
+
+ // Set up the data structures for building the graph.
+ JS::AutoAssertNoGC nogc;
+ JS::AutoEnterCycleCollection autocc(mCCJSRuntime->Runtime());
+ mGraph.Init();
+ mResults.Init();
+ mResults.mSuspectedAtCCStart = SuspectedCount();
+ mResults.mAnyManual = aIsManual;
+ bool mergeZones = ShouldMergeZones(aIsManual);
+ mResults.mMergedZones = mergeZones;
+
+ MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder");
+ mBuilder = MakeUnique<CCGraphBuilder>(mGraph, mResults, mCCJSRuntime, mLogger,
+ mergeZones);
+ timeLog.Checkpoint("BeginCollection prepare graph builder");
+
+ if (mCCJSRuntime) {
+ mCCJSRuntime->TraverseRoots(*mBuilder);
+ timeLog.Checkpoint("mJSContext->TraverseRoots()");
+ }
+
+ AutoRestore<bool> ar(mScanInProgress);
+ MOZ_RELEASE_ASSERT(!mScanInProgress);
+ mScanInProgress = true;
+ mPurpleBuf.SelectPointers(*mBuilder);
+ timeLog.Checkpoint("SelectPointers()");
+
+ mBuilder->DoneAddingRoots();
+ mIncrementalPhase = GraphBuildingPhase;
+}
+
+uint32_t nsCycleCollector::SuspectedCount() {
+ CheckThreadSafety();
+ if (NS_IsMainThread()) {
+ return gNurseryPurpleBufferEntryCount + mPurpleBuf.Count();
+ }
+
+ return mPurpleBuf.Count();
+}
+
+void nsCycleCollector::Shutdown(bool aDoCollect) {
+ CheckThreadSafety();
+
+ if (NS_IsMainThread()) {
+ gNurseryPurpleBufferEnabled = false;
+ }
+
+ // Always delete snow white objects.
+ FreeSnowWhite(true);
+
+ if (aDoCollect) {
+ ShutdownCollect();
+ }
+
+ if (mJSPurpleBuffer) {
+ mJSPurpleBuffer->Destroy();
+ }
+}
+
+void nsCycleCollector::RemoveObjectFromGraph(void* aObj) {
+ if (IsIdle()) {
+ return;
+ }
+
+ mGraph.RemoveObjectFromMap(aObj);
+ if (mBuilder) {
+ mBuilder->RemoveCachedEntry(aObj);
+ }
+}
+
+void nsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ size_t* aObjectSize,
+ size_t* aGraphSize,
+ size_t* aPurpleBufferSize) const {
+ *aObjectSize = aMallocSizeOf(this);
+
+ *aGraphSize = mGraph.SizeOfExcludingThis(aMallocSizeOf);
+
+ *aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf);
+
+ // These fields are deliberately not measured:
+ // - mCCJSRuntime: because it's non-owning and measured by JS reporters.
+ // - mParams: because it only contains scalars.
+}
+
+JSPurpleBuffer* nsCycleCollector::GetJSPurpleBuffer() {
+ if (!mJSPurpleBuffer) {
+ // The Release call here confuses the GC analysis.
+ JS::AutoSuppressGCAnalysis nogc;
+ // JSPurpleBuffer keeps itself alive, but we need to create it in such way
+ // that it ends up in the normal purple buffer. That happens when
+ // nsRefPtr goes out of the scope and calls Release.
+ RefPtr<JSPurpleBuffer> pb = new JSPurpleBuffer(mJSPurpleBuffer);
+ }
+ return mJSPurpleBuffer;
+}
+
+////////////////////////////////////////////////////////////////////////
+// Module public API (exported in nsCycleCollector.h)
+// Just functions that redirect into the singleton, once it's built.
+////////////////////////////////////////////////////////////////////////
+
+void nsCycleCollector_registerJSContext(CycleCollectedJSContext* aCx) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+ // But we shouldn't already have a context.
+ MOZ_ASSERT(!data->mContext);
+
+ data->mContext = aCx;
+ data->mCollector->SetCCJSRuntime(aCx->Runtime());
+}
+
+void nsCycleCollector_forgetJSContext() {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ // And we shouldn't have already forgotten our context.
+ MOZ_ASSERT(data->mContext);
+
+ // But it may have shutdown already.
+ if (data->mCollector) {
+ data->mCollector->ClearCCJSRuntime();
+ data->mContext = nullptr;
+ } else {
+ data->mContext = nullptr;
+ delete data;
+ sCollectorData.set(nullptr);
+ }
+}
+
+/* static */
+CycleCollectedJSContext* CycleCollectedJSContext::Get() {
+ CollectorData* data = sCollectorData.get();
+ if (data) {
+ return data->mContext;
+ }
+ return nullptr;
+}
+
+MOZ_NEVER_INLINE static void SuspectAfterShutdown(
+ void* aPtr, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt, bool* aShouldDelete) {
+ if (aRefCnt->get() == 0) {
+ if (!aShouldDelete) {
+ // The CC is shut down, so we can't be in the middle of an ICC.
+ ToParticipant(aPtr, &aCp);
+ aRefCnt->stabilizeForDeletion();
+ aCp->DeleteCycleCollectable(aPtr);
+ } else {
+ *aShouldDelete = true;
+ }
+ } else {
+ // Make sure we'll get called again.
+ aRefCnt->RemoveFromPurpleBuffer();
+ }
+}
+
+void NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt,
+ bool* aShouldDelete) {
+ if ((
+#ifdef HAVE_64BIT_BUILD
+ aRefCnt->IsOnMainThread() ||
+#endif
+ NS_IsMainThread()) &&
+ gNurseryPurpleBufferEnabled) {
+ // The next time the object is passed to the purple buffer, we can do faster
+ // IsOnMainThread() check.
+ aRefCnt->SetIsOnMainThread();
+ SuspectUsingNurseryPurpleBuffer(aPtr, aCp, aRefCnt);
+ return;
+ }
+
+ CollectorData* data = sCollectorData.get();
+
+ // This assertion will happen if you AddRef or Release a cycle collected
+ // object on a thread that does not have an active cycle collector.
+ // This can happen in a few situations:
+ // 1. We never cycle collect on this thread. (The cycle collector is only
+ // run on the main thread and DOM worker threads.)
+ // 2. The cycle collector hasn't been initialized on this thread yet.
+ // 3. The cycle collector has already been shut down on this thread.
+ MOZ_DIAGNOSTIC_ASSERT(
+ data,
+ "Cycle collected object used on a thread without a cycle collector.");
+
+ if (MOZ_LIKELY(data->mCollector)) {
+ data->mCollector->Suspect(aPtr, aCp, aRefCnt);
+ return;
+ }
+ SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete);
+}
+
+void ClearNurseryPurpleBuffer() {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ CollectorData* data = sCollectorData.get();
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+ data->mCollector->SuspectNurseryEntries();
+}
+
+uint32_t nsCycleCollector_suspectedCount() {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+
+ if (!data->mCollector) {
+ return 0;
+ }
+
+ return data->mCollector->SuspectedCount();
+}
+
+bool nsCycleCollector_init() {
+#ifdef DEBUG
+ static bool sInitialized;
+
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ MOZ_ASSERT(!sInitialized, "Called twice!?");
+ sInitialized = true;
+#endif
+
+ return sCollectorData.init();
+}
+
+void nsCycleCollector_startup() {
+ if (sCollectorData.get()) {
+ MOZ_CRASH();
+ }
+
+ CollectorData* data = new CollectorData;
+ data->mCollector = new nsCycleCollector();
+ data->mContext = nullptr;
+
+ sCollectorData.set(data);
+}
+
+void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+
+ data->mCollector->SetBeforeUnlinkCallback(aCB);
+}
+
+void nsCycleCollector_setForgetSkippableCallback(
+ CC_ForgetSkippableCallback aCB) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+
+ data->mCollector->SetForgetSkippableCallback(aCB);
+}
+
+void nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes,
+ bool aAsyncSnowWhiteFreeing) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+
+ TimeLog timeLog;
+ data->mCollector->ForgetSkippable(aBudget, aRemoveChildlessNodes,
+ aAsyncSnowWhiteFreeing);
+ timeLog.Checkpoint("ForgetSkippable()");
+}
+
+void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation,
+ bool aPurge) {
+ CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
+ if (rt) {
+ rt->DispatchDeferredDeletion(aContinuation, aPurge);
+ }
+}
+
+bool nsCycleCollector_doDeferredDeletion() {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+ MOZ_ASSERT(data->mContext);
+
+ return data->mCollector->FreeSnowWhite(false);
+}
+
+bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+ MOZ_ASSERT(data->mContext);
+
+ return data->mCollector->FreeSnowWhiteWithBudget(aBudget);
+}
+
+already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink() {
+ nsCOMPtr<nsICycleCollectorLogSink> sink = new nsCycleCollectorLogSinkToFile();
+ return sink.forget();
+}
+
+bool nsCycleCollector_collect(CCReason aReason,
+ nsICycleCollectorListener* aManualListener) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+
+ AUTO_PROFILER_LABEL("nsCycleCollector_collect", GCCC);
+
+ SliceBudget unlimitedBudget = SliceBudget::unlimited();
+ return data->mCollector->Collect(aReason, ccIsManual::CCIsManual,
+ unlimitedBudget, aManualListener);
+}
+
+void nsCycleCollector_collectSlice(SliceBudget& budget, CCReason aReason,
+ bool aPreferShorterSlices) {
+ CollectorData* data = sCollectorData.get();
+
+ // We should have started the cycle collector by now.
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->mCollector);
+
+ AUTO_PROFILER_LABEL("nsCycleCollector_collectSlice", GCCC);
+
+ data->mCollector->Collect(aReason, ccIsManual::CCIsNotManual, budget, nullptr,
+ aPreferShorterSlices);
+}
+
+void nsCycleCollector_prepareForGarbageCollection() {
+ CollectorData* data = sCollectorData.get();
+
+ MOZ_ASSERT(data);
+
+ if (!data->mCollector) {
+ return;
+ }
+
+ data->mCollector->PrepareForGarbageCollection();
+}
+
+void nsCycleCollector_finishAnyCurrentCollection() {
+ CollectorData* data = sCollectorData.get();
+
+ MOZ_ASSERT(data);
+
+ if (!data->mCollector) {
+ return;
+ }
+
+ data->mCollector->FinishAnyCurrentCollection(CCReason::API);
+}
+
+void nsCycleCollector_shutdown(bool aDoCollect) {
+ CollectorData* data = sCollectorData.get();
+
+ if (data) {
+ MOZ_ASSERT(data->mCollector);
+ AUTO_PROFILER_LABEL("nsCycleCollector_shutdown", OTHER);
+
+ {
+ RefPtr<nsCycleCollector> collector = data->mCollector;
+ collector->Shutdown(aDoCollect);
+ data->mCollector = nullptr;
+ }
+
+ if (!data->mContext) {
+ delete data;
+ sCollectorData.set(nullptr);
+ }
+ }
+}
diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h
new file mode 100644
index 0000000000..1c583e04cf
--- /dev/null
+++ b/xpcom/base/nsCycleCollector.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCycleCollector_h__
+#define nsCycleCollector_h__
+
+class nsICycleCollectorListener;
+class nsICycleCollectorLogSink;
+class nsISupports;
+template <class T>
+struct already_AddRefed;
+
+#include <cstdint>
+#include "mozilla/Attributes.h"
+
+namespace js {
+class SliceBudget;
+}
+
+namespace mozilla {
+class CycleCollectedJSContext;
+} // namespace mozilla
+
+bool nsCycleCollector_init();
+
+void nsCycleCollector_startup();
+
+typedef void (*CC_BeforeUnlinkCallback)(void);
+void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB);
+
+typedef void (*CC_ForgetSkippableCallback)(void);
+void nsCycleCollector_setForgetSkippableCallback(
+ CC_ForgetSkippableCallback aCB);
+
+void nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget,
+ bool aRemoveChildlessNodes = false,
+ bool aAsyncSnowWhiteFreeing = false);
+
+void nsCycleCollector_prepareForGarbageCollection();
+
+// If an incremental cycle collection is in progress, finish it.
+void nsCycleCollector_finishAnyCurrentCollection();
+
+void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false,
+ bool aPurge = false);
+bool nsCycleCollector_doDeferredDeletion();
+bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget);
+
+already_AddRefed<nsICycleCollectorLogSink> nsCycleCollector_createLogSink();
+already_AddRefed<nsICycleCollectorListener> nsCycleCollector_createLogger();
+
+// Run a cycle collection and return whether anything was collected.
+bool nsCycleCollector_collect(mozilla::CCReason aReason,
+ nsICycleCollectorListener* aManualListener);
+
+void nsCycleCollector_collectSlice(js::SliceBudget& budget,
+ mozilla::CCReason aReason,
+ bool aPreferShorterSlices = false);
+
+uint32_t nsCycleCollector_suspectedCount();
+
+// If aDoCollect is true, then run the GC and CC a few times before
+// shutting down the CC completely.
+MOZ_CAN_RUN_SCRIPT
+void nsCycleCollector_shutdown(bool aDoCollect = true);
+
+// Helpers for interacting with JS
+void nsCycleCollector_registerJSContext(mozilla::CycleCollectedJSContext* aCx);
+void nsCycleCollector_forgetJSContext();
+
+#endif // nsCycleCollector_h__
diff --git a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp
new file mode 100644
index 0000000000..02bc92ddab
--- /dev/null
+++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+#include "nsWrapperCacheInlines.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+void CycleCollectionNoteEdgeNameImpl(
+ nsCycleCollectionTraversalCallback& aCallback, const char* aName,
+ uint32_t aFlags) {
+ nsAutoCString arrayEdgeName(aName);
+ if (aFlags & CycleCollectionEdgeNameArrayFlag) {
+ arrayEdgeName.AppendLiteral("[i]");
+ }
+ aCallback.NoteNextEdgeName(arrayEdgeName.get());
+}
+
+void nsCycleCollectionParticipant::NoteJSChild(JS::GCCellPtr aGCThing,
+ const char* aName,
+ void* aClosure) {
+ nsCycleCollectionTraversalCallback* cb =
+ static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, aName);
+ if (JS::IsCCTraceKind(aGCThing.kind())) {
+ cb->NoteJSChild(aGCThing);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (aPtr->unbarrieredGet().isGCThing()) {
+ mCallback(aPtr->unbarrieredGet().toGCCellPtr(), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<jsid>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (aPtr->unbarrieredGet().isGCThing()) {
+ mCallback(aPtr->unbarrieredGet().toGCCellPtr(), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (*aPtr) {
+ mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(nsWrapperCache* aPtr, const char* aName,
+ void* aClosure) const {
+ JSObject* obj = aPtr->GetWrapperPreserveColor();
+ if (obj) {
+ mCallback(JS::GCCellPtr(obj), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::TenuredHeap<JSObject*>* aPtr,
+ const char* aName, void* aClosure) const {
+ if (*aPtr) {
+ mCallback(JS::GCCellPtr(aPtr->unbarrieredGetPtr()), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (*aPtr) {
+ mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<JSString*>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (*aPtr) {
+ mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure);
+ }
+}
+
+void TraceCallbackFunc::Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
+ void* aClosure) const {
+ if (*aPtr) {
+ mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure);
+ }
+}
diff --git a/xpcom/base/nsDebug.h b/xpcom/base/nsDebug.h
new file mode 100644
index 0000000000..3e3fc4d89e
--- /dev/null
+++ b/xpcom/base/nsDebug.h
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDebug_h___
+#define nsDebug_h___
+
+#include "nscore.h"
+#include "nsError.h"
+
+#include "nsXPCOM.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/DbgMacro.h"
+#include "mozilla/Likely.h"
+#include <stdarg.h>
+
+#ifdef DEBUG
+# include "mozilla/ErrorNames.h"
+# include "mozilla/IntegerPrintfMacros.h"
+# include "mozilla/Printf.h"
+#endif
+
+/**
+ * Warn if the given condition is true. The condition is evaluated in both
+ * release and debug builds, and the result is an expression which can be
+ * used in subsequent expressions, such as:
+ *
+ * if (NS_WARN_IF(NS_FAILED(rv)) {
+ * return rv;
+ * }
+ *
+ * This explicit warning and return is preferred to the NS_ENSURE_* macros
+ * which hide the warning and the return control flow.
+ *
+ * This macro can also be used outside of conditions just to issue a warning,
+ * like so:
+ *
+ * Unused << NS_WARN_IF(NS_FAILED(FnWithSideEffects());
+ *
+ * (The |Unused <<| is necessary because of the [[nodiscard]] annotation.)
+ *
+ * However, note that the argument to this macro is evaluated in all builds. If
+ * you just want a warning assertion, it is better to use NS_WARNING_ASSERTION
+ * (which evaluates the condition only in debug builds) like so:
+ *
+ * NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "operation failed");
+ *
+ * @note This is C++-only
+ */
+#ifdef __cplusplus
+# ifdef DEBUG
+[[nodiscard]] inline bool NS_warn_if_impl(bool aCondition, const char* aExpr,
+ const char* aFile, int32_t aLine) {
+ if (MOZ_UNLIKELY(aCondition)) {
+ NS_DebugBreak(NS_DEBUG_WARNING, nullptr, aExpr, aFile, aLine);
+ }
+ return aCondition;
+}
+# define NS_WARN_IF(condition) \
+ NS_warn_if_impl(condition, #condition, __FILE__, __LINE__)
+# else
+# define NS_WARN_IF(condition) (bool)(condition)
+# endif
+#endif
+
+/**
+ * Test an assertion for truth. If the expression is not true then
+ * emit a warning.
+ *
+ * Program execution continues past the usage of this macro.
+ *
+ * Note also that the non-debug version of this macro does <b>not</b>
+ * evaluate the message argument.
+ */
+#ifdef DEBUG
+# define NS_WARNING_ASSERTION(_expr, _msg) \
+ do { \
+ if (!(_expr)) { \
+ NS_DebugBreak(NS_DEBUG_WARNING, _msg, #_expr, __FILE__, __LINE__); \
+ } \
+ } while (false)
+#else
+# define NS_WARNING_ASSERTION(_expr, _msg) \
+ do { /* nothing */ \
+ } while (false)
+#endif
+
+/**
+ * Test an assertion for truth. If the expression is not true then
+ * trigger a program failure.
+ *
+ * Note that the non-debug version of this macro does <b>not</b>
+ * evaluate the message argument.
+ */
+#ifdef DEBUG
+inline void MOZ_PretendNoReturn() MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS {}
+# define NS_ASSERTION(expr, str) \
+ do { \
+ if (!(expr)) { \
+ NS_DebugBreak(NS_DEBUG_ASSERTION, str, #expr, __FILE__, __LINE__); \
+ MOZ_PretendNoReturn(); \
+ } \
+ } while (0)
+#else
+# define NS_ASSERTION(expr, str) \
+ do { /* nothing */ \
+ } while (0)
+#endif
+
+/**
+ * Log an error message.
+ */
+#ifdef DEBUG
+# define NS_ERROR(str) \
+ do { \
+ NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Error", __FILE__, __LINE__); \
+ MOZ_PretendNoReturn(); \
+ } while (0)
+#else
+# define NS_ERROR(str) \
+ do { /* nothing */ \
+ } while (0)
+#endif
+
+/**
+ * Log a warning message.
+ */
+#ifdef DEBUG
+# define NS_WARNING(str) \
+ NS_DebugBreak(NS_DEBUG_WARNING, str, nullptr, __FILE__, __LINE__)
+#else
+# define NS_WARNING(str) \
+ do { /* nothing */ \
+ } while (0)
+#endif
+
+/**
+ * Trigger a debugger breakpoint, only in debug builds.
+ */
+#ifdef DEBUG
+# define NS_BREAK() \
+ do { \
+ NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, __FILE__, __LINE__); \
+ MOZ_PretendNoReturn(); \
+ } while (0)
+#else
+# define NS_BREAK() \
+ do { /* nothing */ \
+ } while (0)
+#endif
+
+/******************************************************************************
+** Macros for static assertions. These are used by the sixgill tool.
+** When the tool is not running these macros are no-ops.
+******************************************************************************/
+
+/* Avoid name collision if included with other headers defining annotations. */
+#ifndef HAVE_STATIC_ANNOTATIONS
+# define HAVE_STATIC_ANNOTATIONS
+
+# ifdef XGILL_PLUGIN
+
+# define STATIC_PRECONDITION(COND) __attribute__((precondition(#COND)))
+# define STATIC_PRECONDITION_ASSUME(COND) \
+ __attribute__((precondition_assume(#COND)))
+# define STATIC_POSTCONDITION(COND) __attribute__((postcondition(#COND)))
+# define STATIC_POSTCONDITION_ASSUME(COND) \
+ __attribute__((postcondition_assume(#COND)))
+# define STATIC_INVARIANT(COND) __attribute__((invariant(#COND)))
+# define STATIC_INVARIANT_ASSUME(COND) \
+ __attribute__((invariant_assume(#COND)))
+
+/* Used to make identifiers for assert/assume annotations in a function. */
+# define STATIC_PASTE2(X, Y) X##Y
+# define STATIC_PASTE1(X, Y) STATIC_PASTE2(X, Y)
+
+# define STATIC_ASSUME(COND) \
+ do { \
+ __attribute__((assume_static(#COND), unused)) int STATIC_PASTE1( \
+ assume_static_, __COUNTER__); \
+ } while (false)
+
+# define STATIC_ASSERT_RUNTIME(COND) \
+ do { \
+ __attribute__((assert_static_runtime(#COND), \
+ unused)) int STATIC_PASTE1(assert_static_runtime_, \
+ __COUNTER__); \
+ } while (false)
+
+# else /* XGILL_PLUGIN */
+
+# define STATIC_PRECONDITION(COND) /* nothing */
+# define STATIC_PRECONDITION_ASSUME(COND) /* nothing */
+# define STATIC_POSTCONDITION(COND) /* nothing */
+# define STATIC_POSTCONDITION_ASSUME(COND) /* nothing */
+# define STATIC_INVARIANT(COND) /* nothing */
+# define STATIC_INVARIANT_ASSUME(COND) /* nothing */
+
+# define STATIC_ASSUME(COND) \
+ do { /* nothing */ \
+ } while (false)
+# define STATIC_ASSERT_RUNTIME(COND) \
+ do { /* nothing */ \
+ } while (false)
+
+# endif /* XGILL_PLUGIN */
+
+# define STATIC_SKIP_INFERENCE STATIC_INVARIANT(skip_inference())
+
+#endif /* HAVE_STATIC_ANNOTATIONS */
+
+/******************************************************************************
+** Macros for terminating execution when an unrecoverable condition is
+** reached. These need to be compiled regardless of the DEBUG flag.
+******************************************************************************/
+
+/* Macros for checking the trueness of an expression passed in within an
+ * interface implementation. These need to be compiled regardless of the
+ * DEBUG flag. New code should use NS_WARN_IF(condition) instead!
+ * @status deprecated
+ */
+
+#define NS_ENSURE_TRUE(x, ret) \
+ do { \
+ if (MOZ_UNLIKELY(!(x))) { \
+ NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \
+ return ret; \
+ } \
+ } while (false)
+
+#define NS_ENSURE_FALSE(x, ret) NS_ENSURE_TRUE(!(x), ret)
+
+#define NS_ENSURE_TRUE_VOID(x) \
+ do { \
+ if (MOZ_UNLIKELY(!(x))) { \
+ NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \
+ return; \
+ } \
+ } while (false)
+
+#define NS_ENSURE_FALSE_VOID(x) NS_ENSURE_TRUE_VOID(!(x))
+
+/******************************************************************************
+** Macros for checking results
+******************************************************************************/
+
+#if defined(DEBUG) && !defined(XPCOM_GLUE_AVOID_NSPR)
+
+# define NS_ENSURE_SUCCESS_BODY(res, ret) \
+ const char* name = mozilla::GetStaticErrorName(__rv); \
+ mozilla::SmprintfPointer msg = mozilla::Smprintf( \
+ "NS_ENSURE_SUCCESS(%s, %s) failed with " \
+ "result 0x%" PRIX32 "%s%s%s", \
+ #res, #ret, static_cast<uint32_t>(__rv), name ? " (" : "", \
+ name ? name : "", name ? ")" : ""); \
+ NS_WARNING(msg.get());
+
+# define NS_ENSURE_SUCCESS_BODY_VOID(res) \
+ const char* name = mozilla::GetStaticErrorName(__rv); \
+ mozilla::SmprintfPointer msg = mozilla::Smprintf( \
+ "NS_ENSURE_SUCCESS_VOID(%s) failed with " \
+ "result 0x%" PRIX32 "%s%s%s", \
+ #res, static_cast<uint32_t>(__rv), name ? " (" : "", name ? name : "", \
+ name ? ")" : ""); \
+ NS_WARNING(msg.get());
+
+#else
+
+# define NS_ENSURE_SUCCESS_BODY(res, ret) \
+ NS_WARNING("NS_ENSURE_SUCCESS(" #res ", " #ret ") failed");
+
+# define NS_ENSURE_SUCCESS_BODY_VOID(res) \
+ NS_WARNING("NS_ENSURE_SUCCESS_VOID(" #res ") failed");
+
+#endif
+
+#define NS_ENSURE_SUCCESS(res, ret) \
+ do { \
+ nsresult __rv = res; /* Don't evaluate |res| more than once */ \
+ if (NS_FAILED(__rv)) { \
+ NS_ENSURE_SUCCESS_BODY(res, ret) \
+ return ret; \
+ } \
+ } while (false)
+
+#define NS_ENSURE_SUCCESS_VOID(res) \
+ do { \
+ nsresult __rv = res; \
+ if (NS_FAILED(__rv)) { \
+ NS_ENSURE_SUCCESS_BODY_VOID(res) \
+ return; \
+ } \
+ } while (false)
+
+/******************************************************************************
+** Macros for checking state and arguments upon entering interface boundaries
+******************************************************************************/
+
+#define NS_ENSURE_ARG(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_ARG)
+
+#define NS_ENSURE_ARG_POINTER(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_POINTER)
+
+#define NS_ENSURE_ARG_MIN(arg, min) \
+ NS_ENSURE_TRUE((arg) >= min, NS_ERROR_INVALID_ARG)
+
+#define NS_ENSURE_ARG_MAX(arg, max) \
+ NS_ENSURE_TRUE((arg) <= max, NS_ERROR_INVALID_ARG)
+
+#define NS_ENSURE_ARG_RANGE(arg, min, max) \
+ NS_ENSURE_TRUE(((arg) >= min) && ((arg) <= max), NS_ERROR_INVALID_ARG)
+
+#define NS_ENSURE_STATE(state) NS_ENSURE_TRUE(state, NS_ERROR_UNEXPECTED)
+
+/*****************************************************************************/
+
+#if (defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))) && \
+ !defined(XPCOM_GLUE_AVOID_NSPR)
+# define MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+void NS_ABORT_OOM(size_t aSize);
+#else
+inline void NS_ABORT_OOM(size_t) { MOZ_CRASH(); }
+#endif
+
+#endif /* nsDebug_h___ */
diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp
new file mode 100644
index 0000000000..4023efd0ec
--- /dev/null
+++ b/xpcom/base/nsDebugImpl.cpp
@@ -0,0 +1,675 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Chromium headers must come before Mozilla headers.
+#include "base/process_util.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/Printf.h"
+#include "mozilla/ProfilerMarkers.h"
+
+#include "MainThreadUtils.h"
+#include "nsDebugImpl.h"
+#include "nsDebug.h"
+#include "nsExceptionHandler.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "prerror.h"
+#include "prerr.h"
+#include "prenv.h"
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifdef _WIN32
+/* for getenv() */
+# include <stdlib.h>
+#endif
+
+#include "mozilla/StackWalk.h"
+
+#if defined(XP_UNIX)
+# include <signal.h>
+#endif
+
+#if defined(XP_WIN)
+# include <tchar.h>
+# include "nsString.h"
+#endif
+
+#if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__OpenBSD__)
+# include <stdbool.h>
+# include <unistd.h>
+# include <sys/param.h>
+# include <sys/sysctl.h>
+#endif
+
+#if defined(__OpenBSD__)
+# include <sys/proc.h>
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+# include <sys/user.h>
+#endif
+
+#if defined(__NetBSD__)
+# undef KERN_PROC
+# define KERN_PROC KERN_PROC2
+# define KINFO_PROC struct kinfo_proc2
+#else
+# define KINFO_PROC struct kinfo_proc
+#endif
+
+#if defined(XP_MACOSX)
+# define KP_FLAGS kp_proc.p_flag
+#elif defined(__DragonFly__)
+# define KP_FLAGS kp_flags
+#elif defined(__FreeBSD__)
+# define KP_FLAGS ki_flag
+#elif defined(__OpenBSD__) && !defined(_P_TRACED)
+# define KP_FLAGS p_psflags
+# define P_TRACED PS_TRACED
+#else
+# define KP_FLAGS p_flag
+#endif
+
+static void Abort(const char* aMsg);
+
+static void RealBreak();
+
+static void Break(const char* aMsg);
+
+#if defined(_WIN32)
+# include <windows.h>
+# include <signal.h>
+# include <malloc.h> // for _alloca
+#endif
+
+using namespace mozilla;
+
+static const char* sMultiprocessDescription = nullptr;
+
+static Atomic<int32_t> gAssertionCount;
+
+NS_IMPL_QUERY_INTERFACE(nsDebugImpl, nsIDebug2)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsDebugImpl::AddRef() { return 2; }
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsDebugImpl::Release() { return 1; }
+
+NS_IMETHODIMP
+nsDebugImpl::Assertion(const char* aStr, const char* aExpr, const char* aFile,
+ int32_t aLine) {
+ NS_DebugBreak(NS_DEBUG_ASSERTION, aStr, aExpr, aFile, aLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::Warning(const char* aStr, const char* aFile, int32_t aLine) {
+ NS_DebugBreak(NS_DEBUG_WARNING, aStr, nullptr, aFile, aLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::Break(const char* aFile, int32_t aLine) {
+ NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, aFile, aLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::Abort(const char* aFile, int32_t aLine) {
+ NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::CrashWithOOM() {
+ NS_ABORT_OOM(-1);
+ return NS_OK;
+}
+
+// From toolkit/library/rust/lib.rs
+extern "C" void intentional_panic(const char* message);
+
+NS_IMETHODIMP
+nsDebugImpl::RustPanic(const char* aMessage) {
+ intentional_panic(aMessage);
+ return NS_OK;
+}
+
+// From toolkit/library/rust/lib.rs
+extern "C" void debug_log(const char* target, const char* message);
+
+NS_IMETHODIMP
+nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) {
+ debug_log(aTarget, aMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::GetIsDebugBuild(bool* aResult) {
+#ifdef DEBUG
+ *aResult = true;
+#else
+ *aResult = false;
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::GetAssertionCount(int32_t* aResult) {
+ *aResult = gAssertionCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDebugImpl::GetIsDebuggerAttached(bool* aResult) {
+ *aResult = false;
+
+#if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ // no access to KERN_PROC_PID sysctl when pledge'd
+ return NS_OK;
+#endif
+#if defined(XP_WIN)
+ *aResult = ::IsDebuggerPresent();
+#elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__OpenBSD__)
+ // Specify the info we're looking for
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+# if defined(__NetBSD__) || defined(__OpenBSD__)
+ sizeof(KINFO_PROC),
+ 1,
+# endif
+ };
+ u_int mibSize = sizeof(mib) / sizeof(int);
+
+ KINFO_PROC info;
+ size_t infoSize = sizeof(info);
+ memset(&info, 0, infoSize);
+
+ if (sysctl(mib, mibSize, &info, &infoSize, nullptr, 0)) {
+ // if the call fails, default to false
+ *aResult = false;
+ return NS_OK;
+ }
+
+ if (info.KP_FLAGS & P_TRACED) {
+ *aResult = true;
+ }
+#endif
+
+ return NS_OK;
+}
+
+/* static */
+void nsDebugImpl::SetMultiprocessMode(const char* aDesc) {
+ sMultiprocessDescription = aDesc;
+}
+
+/* static */ const char* nsDebugImpl::GetMultiprocessMode() {
+ return sMultiprocessDescription;
+}
+
+/**
+ * Implementation of the nsDebug methods. Note that this code is
+ * always compiled in, in case some other module that uses it is
+ * compiled with debugging even if this library is not.
+ */
+enum nsAssertBehavior {
+ NS_ASSERT_UNINITIALIZED,
+ NS_ASSERT_WARN,
+ NS_ASSERT_SUSPEND,
+ NS_ASSERT_STACK,
+ NS_ASSERT_TRAP,
+ NS_ASSERT_ABORT,
+ NS_ASSERT_STACK_AND_ABORT
+};
+
+static nsAssertBehavior GetAssertBehavior() {
+ static nsAssertBehavior gAssertBehavior = NS_ASSERT_UNINITIALIZED;
+ if (gAssertBehavior != NS_ASSERT_UNINITIALIZED) {
+ return gAssertBehavior;
+ }
+
+ gAssertBehavior = NS_ASSERT_WARN;
+
+ const char* assertString = PR_GetEnv("XPCOM_DEBUG_BREAK");
+ if (!assertString || !*assertString) {
+ return gAssertBehavior;
+ }
+ if (!strcmp(assertString, "warn")) {
+ return gAssertBehavior = NS_ASSERT_WARN;
+ }
+ if (!strcmp(assertString, "suspend")) {
+ return gAssertBehavior = NS_ASSERT_SUSPEND;
+ }
+ if (!strcmp(assertString, "stack")) {
+ return gAssertBehavior = NS_ASSERT_STACK;
+ }
+ if (!strcmp(assertString, "abort")) {
+ return gAssertBehavior = NS_ASSERT_ABORT;
+ }
+ if (!strcmp(assertString, "trap") || !strcmp(assertString, "break")) {
+ return gAssertBehavior = NS_ASSERT_TRAP;
+ }
+ if (!strcmp(assertString, "stack-and-abort")) {
+ return gAssertBehavior = NS_ASSERT_STACK_AND_ABORT;
+ }
+
+ fprintf(stderr, "Unrecognized value of XPCOM_DEBUG_BREAK\n");
+ return gAssertBehavior;
+}
+
+struct FixedBuffer final : public mozilla::PrintfTarget {
+ FixedBuffer() : curlen(0) { buffer[0] = '\0'; }
+
+ char buffer[764];
+ uint32_t curlen;
+
+ bool append(const char* sp, size_t len) override;
+};
+
+bool FixedBuffer::append(const char* aBuf, size_t aLen) {
+ if (!aLen) {
+ return true;
+ }
+
+ if (curlen + aLen >= sizeof(buffer)) {
+ aLen = sizeof(buffer) - curlen - 1;
+ }
+
+ if (aLen) {
+ memcpy(buffer + curlen, aBuf, aLen);
+ curlen += aLen;
+ buffer[curlen] = '\0';
+ }
+
+ return true;
+}
+
+namespace geckoprofiler::markers {
+
+struct DebugBreakMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("DebugBreak");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ uint32_t aSeverity,
+ const ProfilerString8View& aStr,
+ const ProfilerString8View& aExpr,
+ const ProfilerString8View& aFile,
+ int32_t aLine) {
+ nsAutoCString sevString("WARNING");
+ switch (aSeverity) {
+ case NS_DEBUG_ASSERTION:
+ sevString = "ASSERTION";
+ break;
+
+ case NS_DEBUG_BREAK:
+ sevString = "BREAK";
+ break;
+
+ case NS_DEBUG_ABORT:
+ sevString = "ABORT";
+ break;
+ }
+ aWriter.StringProperty("Severity", sevString);
+ // The 'name' property is searchable on the front-end.
+ if (aStr.Length() != 0) {
+ aWriter.StringProperty("Message", aStr);
+ aWriter.StringProperty("name", aStr);
+ } else if (aExpr.Length() != 0) {
+ aWriter.StringProperty("name", aExpr);
+ }
+ if (aExpr.Length() != 0) {
+ aWriter.StringProperty("Expression", aExpr);
+ }
+ if (aFile.Length() != 0) {
+ aWriter.StringProperty("File", aFile);
+ }
+ if (aLine != 0) {
+ aWriter.IntProperty("Line", aLine);
+ }
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::TimelineOverview, MS::Location::MarkerChart,
+ MS::Location::MarkerTable};
+ schema.SetAllLabels("{marker.data.Severity}: {marker.data.name}");
+ schema.AddKeyFormat("Message", MS::Format::String);
+ schema.AddKeyFormat("Severity", MS::Format::String);
+ schema.AddKeyFormat("Expression", MS::Format::String);
+ schema.AddKeyFormat("File", MS::Format::String);
+ schema.AddKeyFormat("Line", MS::Format::Integer);
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+EXPORT_XPCOM_API(void)
+NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr,
+ const char* aFile, int32_t aLine) {
+ FixedBuffer nonPIDBuf;
+ FixedBuffer buf;
+ const char* sevString = "WARNING";
+
+ switch (aSeverity) {
+ case NS_DEBUG_ASSERTION:
+ sevString = "###!!! ASSERTION";
+ break;
+
+ case NS_DEBUG_BREAK:
+ sevString = "###!!! BREAK";
+ break;
+
+ case NS_DEBUG_ABORT:
+ sevString = "###!!! ABORT";
+ break;
+
+ default:
+ aSeverity = NS_DEBUG_WARNING;
+ }
+
+ nonPIDBuf.print("%s: ", sevString);
+ if (aStr) {
+ nonPIDBuf.print("%s: ", aStr);
+ }
+ if (aExpr) {
+ nonPIDBuf.print("'%s', ", aExpr);
+ }
+ if (aFile || aLine != -1) {
+ nonPIDBuf.print("file %s:%d", aFile ? aFile : "<unknown>",
+ aLine != -1 ? aLine : 0);
+ }
+
+ // Print "[PID]" or "[Desc PID]" at the beginning of the message.
+ buf.print("[");
+ if (sMultiprocessDescription) {
+ buf.print("%s ", sMultiprocessDescription);
+ }
+
+ bool isMainthread = (NS_IsMainThreadTLSInitialized() && NS_IsMainThread());
+ PRThread* currentThread = PR_GetCurrentThread();
+ const char* currentThreadName =
+ isMainthread ? "Main Thread" : PR_GetThreadName(currentThread);
+ if (currentThreadName) {
+ buf.print("%" PRIPID ", %s] %s", base::GetCurrentProcId(),
+ currentThreadName, nonPIDBuf.buffer);
+ } else {
+ buf.print("%" PRIPID ", Unnamed thread %p] %s", base::GetCurrentProcId(),
+ currentThread, nonPIDBuf.buffer);
+ }
+
+ // errors on platforms without a debugdlg ring a bell on stderr
+#if !defined(XP_WIN)
+ if (aSeverity != NS_DEBUG_WARNING) {
+ fprintf(stderr, "\07");
+ }
+#endif
+
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer);
+#endif
+
+ PROFILER_MARKER("NS_DebugBreak", OTHER, MarkerStack::Capture(),
+ DebugBreakMarker, aSeverity,
+ ProfilerString8View::WrapNullTerminatedString(aStr),
+ ProfilerString8View::WrapNullTerminatedString(aExpr),
+ ProfilerString8View::WrapNullTerminatedString(aFile), aLine);
+
+ // Write the message to stderr unless it's a warning and MOZ_IGNORE_WARNINGS
+ // is set.
+ if (!(PR_GetEnv("MOZ_IGNORE_WARNINGS") && aSeverity == NS_DEBUG_WARNING)) {
+ fprintf(stderr, "%s\n", buf.buffer);
+ fflush(stderr);
+ }
+
+ switch (aSeverity) {
+ case NS_DEBUG_WARNING:
+ return;
+
+ case NS_DEBUG_BREAK:
+ Break(buf.buffer);
+ return;
+
+ case NS_DEBUG_ABORT: {
+ // Updating crash annotations in the child causes us to do IPC. This can
+ // really cause trouble if we're asserting from within IPC code. So we
+ // have to do without the annotations in that case.
+ if (XRE_IsParentProcess()) {
+ // Don't include the PID in the crash report annotation to
+ // allow faceting on crash-stats.mozilla.org.
+ nsCString note("xpcom_runtime_abort(");
+ note += nonPIDBuf.buffer;
+ note += ")";
+ CrashReporter::AppendAppNotesToCrashReport(note);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AbortMessage,
+ nsDependentCString(nonPIDBuf.buffer));
+ }
+
+#if defined(DEBUG) && defined(_WIN32)
+ RealBreak();
+#endif
+#if defined(DEBUG)
+ MozWalkTheStack(stderr);
+#endif
+ Abort(buf.buffer);
+ return;
+ }
+ }
+
+ // Now we deal with assertions
+ gAssertionCount++;
+
+ switch (GetAssertBehavior()) {
+ case NS_ASSERT_WARN:
+ return;
+
+ case NS_ASSERT_SUSPEND:
+#ifdef XP_UNIX
+ fprintf(stderr, "Suspending process; attach with the debugger.\n");
+ kill(0, SIGSTOP);
+#else
+ Break(buf.buffer);
+#endif
+ return;
+
+ case NS_ASSERT_STACK:
+ MozWalkTheStack(stderr);
+ return;
+
+ case NS_ASSERT_STACK_AND_ABORT:
+ MozWalkTheStack(stderr);
+ // Fall through to abort
+ [[fallthrough]];
+
+ case NS_ASSERT_ABORT:
+ Abort(buf.buffer);
+ return;
+
+ case NS_ASSERT_TRAP:
+ case NS_ASSERT_UNINITIALIZED: // Default to "trap" behavior
+ Break(buf.buffer);
+ return;
+ }
+}
+
+static void Abort(const char* aMsg) {
+ NoteIntentionalCrash(XRE_GetProcessTypeString());
+ MOZ_CRASH_UNSAFE(aMsg);
+}
+
+static void RealBreak() {
+#if defined(_WIN32)
+ ::DebugBreak();
+#elif defined(XP_MACOSX)
+ raise(SIGTRAP);
+#elif defined(__GNUC__) && \
+ (defined(__i386__) || defined(__i386) || defined(__x86_64__))
+ asm("int $3");
+#elif defined(__arm__)
+ asm(
+# ifdef __ARM_ARCH_4T__
+ /* ARMv4T doesn't support the BKPT instruction, so if the compiler target
+ * is ARMv4T, we want to ensure the assembler will understand that ARMv5T
+ * instruction, while keeping the resulting object tagged as ARMv4T.
+ */
+ ".arch armv5t\n"
+ ".object_arch armv4t\n"
+# endif
+ "BKPT #0");
+#elif defined(__aarch64__)
+ asm("brk #0");
+#elif defined(SOLARIS)
+# if defined(__i386__) || defined(__i386) || defined(__x86_64__)
+ asm("int $3");
+# else
+ raise(SIGTRAP);
+# endif
+#else
+# warning do not know how to break on this platform
+#endif
+}
+
+// Abort() calls this function, don't call it!
+static void Break(const char* aMsg) {
+#if defined(_WIN32)
+ static int ignoreDebugger;
+ if (!ignoreDebugger) {
+ const char* shouldIgnoreDebugger = getenv("XPCOM_DEBUG_DLG");
+ ignoreDebugger =
+ 1 + (shouldIgnoreDebugger && !strcmp(shouldIgnoreDebugger, "1"));
+ }
+ if ((ignoreDebugger == 2) || !::IsDebuggerPresent()) {
+ DWORD code = IDRETRY;
+
+ /* Create the debug dialog out of process to avoid the crashes caused by
+ * Windows events leaking into our event loop from an in process dialog.
+ * We do this by launching windbgdlg.exe (built in xpcom/windbgdlg).
+ * See http://bugzilla.mozilla.org/show_bug.cgi?id=54792
+ */
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si;
+ wchar_t executable[MAX_PATH];
+ wchar_t* pName;
+
+ memset(&pi, 0, sizeof(pi));
+
+ memset(&si, 0, sizeof(si));
+ si.cb = sizeof(si);
+ si.wShowWindow = SW_SHOW;
+
+ // 2nd arg of CreateProcess is in/out
+ wchar_t* msgCopy = (wchar_t*)_alloca((strlen(aMsg) + 1) * sizeof(wchar_t));
+ wcscpy(msgCopy, NS_ConvertUTF8toUTF16(aMsg).get());
+
+ if (GetModuleFileNameW(GetModuleHandleW(L"xpcom.dll"), executable,
+ MAX_PATH) &&
+ (pName = wcsrchr(executable, '\\')) != nullptr &&
+ wcscpy(pName + 1, L"windbgdlg.exe") &&
+ CreateProcessW(executable, msgCopy, nullptr, nullptr, false,
+ DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr,
+ nullptr, &si, &pi)) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ GetExitCodeProcess(pi.hProcess, &code);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+
+ switch (code) {
+ case IDABORT:
+ // This should exit us
+ raise(SIGABRT);
+ // If we are ignored exit this way..
+ _exit(3);
+
+ case IDIGNORE:
+ return;
+ }
+ }
+
+ RealBreak();
+#elif defined(XP_MACOSX)
+ /* Note that we put this Mac OS X test above the GNUC/x86 test because the
+ * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86
+ * impls to be the same.
+ */
+ RealBreak();
+#elif defined(__GNUC__) && \
+ (defined(__i386__) || defined(__i386) || defined(__x86_64__))
+ RealBreak();
+#elif defined(__arm__) || defined(__aarch64__)
+ RealBreak();
+#elif defined(SOLARIS)
+ RealBreak();
+#else
+# warning do not know how to break on this platform
+#endif
+}
+
+nsresult nsDebugImpl::Create(const nsIID& aIID, void** aInstancePtr) {
+ static const nsDebugImpl* sImpl;
+
+ if (!sImpl) {
+ sImpl = new nsDebugImpl();
+ }
+
+ return const_cast<nsDebugImpl*>(sImpl)->QueryInterface(aIID, aInstancePtr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult NS_ErrorAccordingToNSPR() {
+ PRErrorCode err = PR_GetError();
+ switch (err) {
+ case PR_OUT_OF_MEMORY_ERROR:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case PR_WOULD_BLOCK_ERROR:
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ case PR_FILE_NOT_FOUND_ERROR:
+ return NS_ERROR_FILE_NOT_FOUND;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ return NS_ERROR_FILE_READ_ONLY;
+ case PR_NOT_DIRECTORY_ERROR:
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ case PR_IS_DIRECTORY_ERROR:
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ case PR_LOOP_ERROR:
+ return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ case PR_FILE_EXISTS_ERROR:
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+ case PR_FILE_IS_LOCKED_ERROR:
+ return NS_ERROR_FILE_IS_LOCKED;
+ case PR_FILE_TOO_BIG_ERROR:
+ return NS_ERROR_FILE_TOO_BIG;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case PR_NAME_TOO_LONG_ERROR:
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ case PR_DIRECTORY_NOT_EMPTY_ERROR:
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+void NS_ABORT_OOM(size_t aSize) {
+ CrashReporter::AnnotateOOMAllocationSize(aSize);
+ MOZ_CRASH("OOM");
+}
diff --git a/xpcom/base/nsDebugImpl.h b/xpcom/base/nsDebugImpl.h
new file mode 100644
index 0000000000..a3adcad3d7
--- /dev/null
+++ b/xpcom/base/nsDebugImpl.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDebugImpl_h
+#define nsDebugImpl_h
+
+#include "nsIDebug2.h"
+
+class nsDebugImpl : public nsIDebug2 {
+ public:
+ nsDebugImpl() = default;
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDEBUG2
+
+ static nsresult Create(const nsIID& aIID, void** aInstancePtr);
+
+ /*
+ * If we are in multiprocess mode, return the process name.
+ */
+ static const char* GetMultiprocessMode();
+
+ /*
+ * Inform nsDebugImpl that we're in multiprocess mode.
+ *
+ * If aDesc is not nullptr, the string it points to must be
+ * statically-allocated (i.e., it must be a string literal).
+ */
+ static void SetMultiprocessMode(const char* aDesc);
+};
+
+#define NS_DEBUG_CONTRACTID "@mozilla.org/xpcom/debug;1"
+#define NS_DEBUG_CID \
+ { /* cb6cdb94-e417-4601-b4a5-f991bf41453d */ \
+ 0xcb6cdb94, 0xe417, 0x4601, { \
+ 0xb4, 0xa5, 0xf9, 0x91, 0xbf, 0x41, 0x45, 0x3d \
+ } \
+ }
+
+#endif // nsDebugImpl_h
diff --git a/xpcom/base/nsDumpUtils.cpp b/xpcom/base/nsDumpUtils.cpp
new file mode 100644
index 0000000000..c7bbbf4eb3
--- /dev/null
+++ b/xpcom/base/nsDumpUtils.cpp
@@ -0,0 +1,488 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDumpUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include <errno.h>
+#include "prenv.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Unused.h"
+#include "SpecialSystemDirectory.h"
+
+#ifdef XP_UNIX // {
+# include "mozilla/Preferences.h"
+# include <fcntl.h>
+# include <unistd.h>
+# include <sys/stat.h>
+
+using namespace mozilla;
+
+/*
+ * The following code supports triggering a registered callback upon
+ * receiving a specific signal.
+ *
+ * Take about:memory for example, we register
+ * 1. doGCCCDump for doMemoryReport
+ * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN)
+ * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1).
+ *
+ * When we receive one of these signals, we write the signal number to a pipe.
+ * The IO thread then notices that the pipe has been written to, and kicks off
+ * the appropriate task on the main thread.
+ *
+ * This scheme is similar to using signalfd(), except it's portable and it
+ * doesn't require the use of sigprocmask, which is problematic because it
+ * masks signals received by child processes.
+ *
+ * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
+ * But that uses libevent, which does not handle the realtime signals (bug
+ * 794074).
+ */
+
+// This is the write-end of a pipe that we use to notice when a
+// specific signal occurs.
+static Atomic<int> sDumpPipeWriteFd(-1);
+
+const char FifoWatcher::kPrefName[] = "memory_info_dumper.watch_fifo.enabled";
+
+static void DumpSignalHandler(int aSignum) {
+ // This is a signal handler, so everything in here needs to be
+ // async-signal-safe. Be careful!
+
+ if (sDumpPipeWriteFd != -1) {
+ uint8_t signum = static_cast<int>(aSignum);
+ Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum));
+ }
+}
+
+NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver);
+
+void FdWatcher::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false);
+
+ XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod(
+ "FdWatcher::StartWatching", this, &FdWatcher::StartWatching));
+}
+
+// Implementations may call this function multiple times if they ensure that
+// it's safe to call OpenFd() multiple times and they call StopWatching()
+// first.
+void FdWatcher::StartWatching() {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+ MOZ_ASSERT(mFd == -1);
+
+ mFd = OpenFd();
+ if (mFd == -1) {
+ LOG("FdWatcher: OpenFd failed.");
+ return;
+ }
+
+ MessageLoopForIO::current()->WatchFileDescriptor(mFd, /* persistent = */ true,
+ MessageLoopForIO::WATCH_READ,
+ &mReadWatcher, this);
+}
+
+// Since implementations can call StartWatching() multiple times, they can of
+// course call StopWatching() multiple times.
+void FdWatcher::StopWatching() {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+ mReadWatcher.StopWatchingFileDescriptor();
+ if (mFd != -1) {
+ close(mFd);
+ mFd = -1;
+ }
+}
+
+StaticRefPtr<SignalPipeWatcher> SignalPipeWatcher::sSingleton;
+
+/* static */
+SignalPipeWatcher* SignalPipeWatcher::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new SignalPipeWatcher();
+ sSingleton->Init();
+ ClearOnShutdown(&sSingleton);
+ }
+ return sSingleton;
+}
+
+void SignalPipeWatcher::RegisterCallback(uint8_t aSignal,
+ PipeCallback aCallback) {
+ MutexAutoLock lock(mSignalInfoLock);
+
+ for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) {
+ if (mSignalInfo[i].mSignal == aSignal) {
+ LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal);
+ return;
+ }
+ }
+ SignalInfo signalInfo = {aSignal, aCallback};
+ mSignalInfo.AppendElement(signalInfo);
+ RegisterSignalHandler(signalInfo.mSignal);
+}
+
+void SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) {
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ sigemptyset(&action.sa_mask);
+ action.sa_handler = DumpSignalHandler;
+
+ if (aSignal) {
+ if (sigaction(aSignal, &action, nullptr)) {
+ LOG("SignalPipeWatcher failed to register sig %d.", aSignal);
+ }
+ } else {
+ MutexAutoLock lock(mSignalInfoLock);
+ for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
+ if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) {
+ LOG("SignalPipeWatcher failed to register signal(%d) "
+ "dump signal handler.",
+ mSignalInfo[i].mSignal);
+ }
+ }
+ }
+}
+
+SignalPipeWatcher::~SignalPipeWatcher() {
+ if (sDumpPipeWriteFd != -1) {
+ StopWatching();
+ }
+}
+
+int SignalPipeWatcher::OpenFd() {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+ // Create a pipe. When we receive a signal in our signal handler, we'll
+ // write the signum to the write-end of this pipe.
+ int pipeFds[2];
+ if (pipe(pipeFds)) {
+ LOG("SignalPipeWatcher failed to create pipe.");
+ return -1;
+ }
+
+ // Close this pipe on calls to exec().
+ fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
+ fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC);
+
+ int readFd = pipeFds[0];
+ sDumpPipeWriteFd = pipeFds[1];
+
+ RegisterSignalHandler();
+ return readFd;
+}
+
+void SignalPipeWatcher::StopWatching() {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+ // Close sDumpPipeWriteFd /after/ setting the fd to -1.
+ // Otherwise we have the (admittedly far-fetched) race where we
+ //
+ // 1) close sDumpPipeWriteFd
+ // 2) open a new fd with the same number as sDumpPipeWriteFd
+ // had.
+ // 3) receive a signal, then write to the fd.
+ int pipeWriteFd = sDumpPipeWriteFd.exchange(-1);
+ close(pipeWriteFd);
+
+ FdWatcher::StopWatching();
+}
+
+void SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+ uint8_t signum;
+ ssize_t numReceived = read(aFd, &signum, sizeof(signum));
+ if (numReceived != sizeof(signum)) {
+ LOG("Error reading from buffer in "
+ "SignalPipeWatcher::OnFileCanReadWithoutBlocking.");
+ return;
+ }
+
+ {
+ MutexAutoLock lock(mSignalInfoLock);
+ for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) {
+ if (signum == mSignalInfo[i].mSignal) {
+ mSignalInfo[i].mCallback(signum);
+ return;
+ }
+ }
+ }
+ LOG("SignalPipeWatcher got unexpected signum.");
+}
+
+StaticRefPtr<FifoWatcher> FifoWatcher::sSingleton;
+
+/* static */
+FifoWatcher* FifoWatcher::GetSingleton() {
+ if (!sSingleton) {
+ nsAutoCString dirPath;
+ Preferences::GetCString("memory_info_dumper.watch_fifo.directory", dirPath);
+ sSingleton = new FifoWatcher(dirPath);
+ sSingleton->Init();
+ ClearOnShutdown(&sSingleton);
+ }
+ return sSingleton;
+}
+
+/* static */
+bool FifoWatcher::MaybeCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ // We want this to be main-process only, since two processes can't listen
+ // to the same fifo.
+ return false;
+ }
+
+ if (!Preferences::GetBool(kPrefName, false)) {
+ LOG("Fifo watcher disabled via pref.");
+ return false;
+ }
+
+ // The FifoWatcher is held alive by the observer service.
+ if (!sSingleton) {
+ GetSingleton();
+ }
+ return true;
+}
+
+void FifoWatcher::RegisterCallback(const nsCString& aCommand,
+ FifoCallback aCallback) {
+ MutexAutoLock lock(mFifoInfoLock);
+
+ for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) {
+ if (mFifoInfo[i].mCommand.Equals(aCommand)) {
+ LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get());
+ return;
+ }
+ }
+ FifoInfo aFifoInfo = {aCommand, aCallback};
+ mFifoInfo.AppendElement(aFifoInfo);
+}
+
+FifoWatcher::~FifoWatcher() = default;
+
+int FifoWatcher::OpenFd() {
+ // If the memory_info_dumper.directory pref is specified, put the fifo
+ // there. Otherwise, put it into the system's tmp directory.
+
+ nsCOMPtr<nsIFile> file;
+
+ nsresult rv;
+ if (mDirPath.Length() > 0) {
+ rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get());
+ return -1;
+ }
+ } else {
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+ }
+
+ rv = file->AppendNative("debug_info_trigger"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ nsAutoCString path;
+ rv = file->GetNativePath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return -1;
+ }
+
+ // unlink might fail because the file doesn't exist, or for other reasons.
+ // But we don't care it fails; any problems will be detected later, when we
+ // try to mkfifo or open the file.
+ if (unlink(path.get())) {
+ LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. "
+ "Continuing despite error.",
+ errno);
+ }
+
+ if (mkfifo(path.get(), 0766)) {
+ LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno);
+ return -1;
+ }
+
+# ifdef ANDROID
+ // Android runs with a umask, so we need to chmod our fifo to make it
+ // world-writable.
+ chmod(path.get(), 0666);
+# endif
+
+ int fd;
+ do {
+ // The fifo will block until someone else has written to it. In
+ // particular, open() will block until someone else has opened it for
+ // writing! We want open() to succeed and read() to block, so we open
+ // with NONBLOCK and then fcntl that away.
+ fd = open(path.get(), O_RDONLY | O_NONBLOCK);
+ } while (fd == -1 && errno == EINTR);
+
+ if (fd == -1) {
+ LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno);
+ return -1;
+ }
+
+ // Make fd blocking now that we've opened it.
+ if (fcntl(fd, F_SETFL, 0)) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+void FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) {
+ MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current());
+
+ char buf[1024];
+ int nread;
+ do {
+ // sizeof(buf) - 1 to leave space for the null-terminator.
+ nread = read(aFd, buf, sizeof(buf));
+ } while (nread == -1 && errno == EINTR);
+
+ if (nread == -1) {
+ // We want to avoid getting into a situation where
+ // OnFileCanReadWithoutBlocking is called in an infinite loop, so when
+ // something goes wrong, stop watching the fifo altogether.
+ LOG("FifoWatcher hit an error (%d) and is quitting.", errno);
+ StopWatching();
+ return;
+ }
+
+ if (nread == 0) {
+ // If we get EOF, that means that the other side closed the fifo. We need
+ // to close and re-open the fifo; if we don't,
+ // OnFileCanWriteWithoutBlocking will be called in an infinite loop.
+
+ LOG("FifoWatcher closing and re-opening fifo.");
+ StopWatching();
+ StartWatching();
+ return;
+ }
+
+ nsAutoCString inputStr;
+ inputStr.Append(buf, nread);
+
+ // Trimming whitespace is important because if you do
+ // |echo "foo" >> debug_info_trigger|,
+ // it'll actually write "foo\n" to the fifo.
+ inputStr.Trim("\b\t\r\n");
+
+ {
+ MutexAutoLock lock(mFifoInfoLock);
+
+ for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) {
+ const nsCString commandStr = mFifoInfo[i].mCommand;
+ if (inputStr == commandStr.get()) {
+ mFifoInfo[i].mCallback(inputStr);
+ return;
+ }
+ }
+ }
+ LOG("Got unexpected value from fifo; ignoring it.");
+}
+
+#endif // XP_UNIX }
+
+// In Android case, this function will open a file named aFilename under
+// /data/local/tmp/"aFoldername".
+// Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR".
+/* static */
+nsresult nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
+ const nsACString& aFoldername, Mode aMode) {
+#ifdef ANDROID
+ // For Android, first try the downloads directory which is world-readable
+ // rather than the temp directory which is not.
+ if (!*aFile) {
+ char* env = PR_GetEnv("DOWNLOADS_DIRECTORY");
+ if (env) {
+ NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile);
+ }
+ }
+#endif
+ nsresult rv;
+ if (!*aFile) {
+ if (NS_IsMainThread()) {
+ // This allows tests to override, but isn't safe off-mainthread.
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile);
+ } else {
+ rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, aFile);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+#ifdef ANDROID
+ // /data/local/tmp is a true tmp directory; anyone can create a file there,
+ // but only the user which created the file can remove it. We want non-root
+ // users to be able to remove these files, so we write them into a
+ // subdirectory of the temp directory and chmod 777 that directory.
+ if (!aFoldername.IsEmpty()) {
+ rv = (*aFile)->AppendNative(aFoldername);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // It's OK if this fails; that probably just means that the directory
+ // already exists.
+ Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777);
+
+ nsAutoCString dirPath;
+ rv = (*aFile)->GetNativePath(dirPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) {
+ }
+ }
+#endif
+
+ nsCOMPtr<nsIFile> file(*aFile);
+
+ rv = file->AppendNative(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aMode == CREATE_UNIQUE) {
+ rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+ } else {
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef ANDROID
+ // Make this file world-read/writable; the permissions passed to the
+ // CreateUnique call above are not sufficient on Android, which runs with a
+ // umask.
+ nsAutoCString path;
+ rv = file->GetNativePath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ while (chmod(path.get(), 0666) == -1 && errno == EINTR) {
+ }
+#endif
+
+ return NS_OK;
+}
diff --git a/xpcom/base/nsDumpUtils.h b/xpcom/base/nsDumpUtils.h
new file mode 100644
index 0000000000..7aa9c6b735
--- /dev/null
+++ b/xpcom/base/nsDumpUtils.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_nsDumpUtils_h
+#define mozilla_nsDumpUtils_h
+
+#include "nsIObserver.h"
+#include "base/message_loop.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTArray.h"
+
+#ifdef LOG
+# undef LOG
+#endif
+
+#ifdef ANDROID
+# include "android/log.h"
+# define LOG(...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gecko:DumpUtils", ##__VA_ARGS__)
+#else
+# define LOG(...)
+#endif
+
+#ifdef XP_UNIX // {
+
+/**
+ * Abstract base class for something which watches an fd and takes action when
+ * we can read from it without blocking.
+ */
+class FdWatcher : public MessageLoopForIO::Watcher, public nsIObserver {
+ protected:
+ MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+ int mFd;
+
+ virtual ~FdWatcher() {
+ // StopWatching should have run.
+ MOZ_ASSERT(mFd == -1);
+ }
+
+ public:
+ FdWatcher() : mFd(-1) { MOZ_ASSERT(NS_IsMainThread()); }
+
+ /**
+ * Open the fd to watch. If we encounter an error, return -1.
+ */
+ virtual int OpenFd() = 0;
+
+ /**
+ * Called when you can read() from the fd without blocking. Note that this
+ * function is also called when you're at eof (read() returns 0 in this case).
+ */
+ virtual void OnFileCanReadWithoutBlocking(int aFd) override = 0;
+ virtual void OnFileCanWriteWithoutBlocking(int aFd) override{};
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * Initialize this object. This should be called right after the object is
+ * constructed. (This would go in the constructor, except we interact with
+ * XPCOM, which we can't do from a constructor because our refcount is 0 at
+ * that point.)
+ */
+ void Init();
+
+ // Implementations may call this function multiple times if they ensure that
+
+ virtual void StartWatching();
+
+ // Since implementations can call StartWatching() multiple times, they can of
+ // course call StopWatching() multiple times.
+ virtual void StopWatching();
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+ XRE_GetIOMessageLoop()->PostTask(mozilla::NewRunnableMethod(
+ "FdWatcher::StopWatching", this, &FdWatcher::StopWatching));
+
+ return NS_OK;
+ }
+};
+
+typedef void (*FifoCallback)(const nsCString& aInputStr);
+struct FifoInfo {
+ nsCString mCommand;
+ FifoCallback mCallback;
+};
+typedef nsTArray<FifoInfo> FifoInfoArray;
+
+class FifoWatcher : public FdWatcher {
+ public:
+ /**
+ * The name of the preference used to enable/disable the FifoWatcher.
+ */
+ // The length of this array must match the size of the string constant in
+ // the definition in nsDumpUtils.cpp. A mismatch will result in a compile-time
+ // error.
+ static const char kPrefName[38];
+
+ static FifoWatcher* GetSingleton();
+
+ static bool MaybeCreate();
+
+ void RegisterCallback(const nsCString& aCommand, FifoCallback aCallback);
+
+ virtual ~FifoWatcher();
+
+ virtual int OpenFd() override;
+
+ virtual void OnFileCanReadWithoutBlocking(int aFd) override;
+
+ private:
+ nsAutoCString mDirPath;
+
+ static mozilla::StaticRefPtr<FifoWatcher> sSingleton;
+
+ explicit FifoWatcher(nsCString aPath)
+ : mDirPath(aPath), mFifoInfoLock("FifoWatcher.mFifoInfoLock") {}
+
+ mozilla::Mutex mFifoInfoLock; // protects mFifoInfo
+ FifoInfoArray mFifoInfo MOZ_GUARDED_BY(mFifoInfoLock);
+};
+
+typedef void (*PipeCallback)(const uint8_t aRecvSig);
+struct SignalInfo {
+ uint8_t mSignal;
+ PipeCallback mCallback;
+};
+typedef nsTArray<SignalInfo> SignalInfoArray;
+
+class SignalPipeWatcher : public FdWatcher {
+ public:
+ static SignalPipeWatcher* GetSingleton();
+
+ void RegisterCallback(uint8_t aSignal, PipeCallback aCallback);
+
+ void RegisterSignalHandler(uint8_t aSignal = 0);
+
+ virtual ~SignalPipeWatcher();
+
+ virtual int OpenFd() override;
+
+ virtual void StopWatching() override;
+
+ virtual void OnFileCanReadWithoutBlocking(int aFd) override;
+
+ private:
+ static mozilla::StaticRefPtr<SignalPipeWatcher> sSingleton;
+
+ SignalPipeWatcher() : mSignalInfoLock("SignalPipeWatcher.mSignalInfoLock") {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ mozilla::Mutex mSignalInfoLock; // protects mSignalInfo
+ SignalInfoArray mSignalInfo MOZ_GUARDED_BY(mSignalInfoLock);
+};
+
+#endif // XP_UNIX }
+
+class nsDumpUtils {
+ public:
+ enum Mode { CREATE, CREATE_UNIQUE };
+
+ /**
+ * This function creates a new unique file based on |aFilename| in a
+ * world-readable temp directory. This is the system temp directory
+ * or, in the case of Android, the downloads directory. If |aFile| is
+ * non-null, it is assumed to point to a folder, and that folder is used
+ * instead.
+ */
+ static nsresult OpenTempFile(const nsACString& aFilename, nsIFile** aFile,
+ const nsACString& aFoldername = ""_ns,
+ Mode aMode = CREATE_UNIQUE);
+};
+
+#endif
diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h
new file mode 100644
index 0000000000..040cd4612c
--- /dev/null
+++ b/xpcom/base/nsError.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsError_h__
+#define nsError_h__
+
+#ifndef __cplusplus
+# error nsError.h no longer supports C sources
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+
+#include <stdint.h>
+
+#define NS_ERROR_SEVERITY_SUCCESS 0
+#define NS_ERROR_SEVERITY_ERROR 1
+
+#include "ErrorList.h" // IWYU pragma: export
+
+/**
+ * @name Standard Error Handling Macros
+ * @return 0 or 1 (false/true with bool type for C++)
+ */
+
+inline uint32_t NS_FAILED_impl(nsresult aErr) {
+ return static_cast<uint32_t>(aErr) & 0x80000000;
+}
+#define NS_FAILED(_nsresult) ((bool)MOZ_UNLIKELY(NS_FAILED_impl(_nsresult)))
+#define NS_SUCCEEDED(_nsresult) ((bool)MOZ_LIKELY(!NS_FAILED_impl(_nsresult)))
+
+/* Check that our enum type is actually uint32_t as expected */
+static_assert(((nsresult)0) < ((nsresult)-1),
+ "nsresult must be an unsigned type");
+static_assert(sizeof(nsresult) == sizeof(uint32_t), "nsresult must be 32 bits");
+
+#define MOZ_ALWAYS_SUCCEEDS(expr) MOZ_ALWAYS_TRUE(NS_SUCCEEDED(expr))
+
+/**
+ * @name Standard Error Generating Macros
+ */
+
+#define NS_ERROR_GENERATE(sev, module, code) \
+ (nsresult)(((uint32_t)(sev) << 31) | \
+ ((uint32_t)(module + NS_ERROR_MODULE_BASE_OFFSET) << 16) | \
+ ((uint32_t)(code)))
+
+#define NS_ERROR_GENERATE_SUCCESS(module, code) \
+ NS_ERROR_GENERATE(NS_ERROR_SEVERITY_SUCCESS, module, code)
+
+#define NS_ERROR_GENERATE_FAILURE(module, code) \
+ NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR, module, code)
+
+/*
+ * This will return the nsresult corresponding to the most recent NSPR failure
+ * returned by PR_GetError.
+ *
+ ***********************************************************************
+ * Do not depend on this function. It will be going away!
+ ***********************************************************************
+ */
+extern nsresult NS_ErrorAccordingToNSPR();
+
+/**
+ * @name Standard Macros for retrieving error bits
+ */
+
+inline constexpr uint16_t NS_ERROR_GET_CODE(nsresult aErr) {
+ return uint32_t(aErr) & 0xffff;
+}
+inline constexpr uint16_t NS_ERROR_GET_MODULE(nsresult aErr) {
+ return ((uint32_t(aErr) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff;
+}
+inline bool NS_ERROR_GET_SEVERITY(nsresult aErr) {
+ return uint32_t(aErr) >> 31;
+}
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4251) /* 'nsCOMPtr<class nsIInputStream>' needs to \
+ have dll-interface to be used by clients \
+ of class 'nsInputStream' */
+# pragma warning( \
+ disable : 4275) /* non dll-interface class 'nsISupports' used as base \
+ for dll-interface class 'nsIRDFNode' */
+#endif
+
+#endif
diff --git a/xpcom/base/nsGZFileWriter.cpp b/xpcom/base/nsGZFileWriter.cpp
new file mode 100644
index 0000000000..2d8e14e944
--- /dev/null
+++ b/xpcom/base/nsGZFileWriter.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGZFileWriter.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "zlib.h"
+
+#ifdef XP_WIN
+# include <io.h>
+# define _dup dup
+#else
+# include <unistd.h>
+#endif
+
+nsGZFileWriter::nsGZFileWriter(Operation aMode)
+ : mMode(aMode), mInitialized(false), mFinished(false), mGZFile(nullptr) {}
+
+nsGZFileWriter::~nsGZFileWriter() {
+ if (mInitialized && !mFinished) {
+ Finish();
+ }
+}
+
+nsresult nsGZFileWriter::Init(nsIFile* aFile) {
+ if (NS_WARN_IF(mInitialized) || NS_WARN_IF(mFinished)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get a FILE out of our nsIFile. Convert that into a file descriptor which
+ // gzip can own. Then close our FILE, leaving only gzip's fd open.
+
+ FILE* file;
+ nsresult rv = aFile->OpenANSIFileDesc(mMode == Create ? "wb" : "ab", &file);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return InitANSIFileDesc(file);
+}
+
+nsresult nsGZFileWriter::InitANSIFileDesc(FILE* aFile) {
+ if (NS_WARN_IF(mInitialized) || NS_WARN_IF(mFinished)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mGZFile = gzdopen(dup(fileno(aFile)), mMode == Create ? "wb" : "ab");
+ fclose(aFile);
+
+ // gzdopen returns nullptr on error.
+ if (NS_WARN_IF(!mGZFile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+nsresult nsGZFileWriter::Write(const nsACString& aStr) {
+ if (NS_WARN_IF(!mInitialized) || NS_WARN_IF(mFinished)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // gzwrite uses a return value of 0 to indicate failure. Otherwise, it
+ // returns the number of uncompressed bytes written. To ensure we can
+ // distinguish between success and failure, don't call gzwrite when we have 0
+ // bytes to write.
+ if (aStr.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // gzwrite never does a short write -- that is, the return value should
+ // always be either 0 or aStr.Length(), and we shouldn't have to call it
+ // multiple times in order to get it to read the whole buffer.
+ int rv = gzwrite(mGZFile, aStr.BeginReading(), aStr.Length());
+ if (NS_WARN_IF(rv != static_cast<int>(aStr.Length()))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsGZFileWriter::Finish() {
+ if (NS_WARN_IF(!mInitialized) || NS_WARN_IF(mFinished)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFinished = true;
+ gzclose(mGZFile);
+
+ // Ignore errors from gzclose; it's not like there's anything we can do about
+ // it, at this point!
+ return NS_OK;
+}
diff --git a/xpcom/base/nsGZFileWriter.h b/xpcom/base/nsGZFileWriter.h
new file mode 100644
index 0000000000..dc33b4a2de
--- /dev/null
+++ b/xpcom/base/nsGZFileWriter.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGZFileWriter_h
+#define nsGZFileWriter_h
+
+#include "nsISupportsImpl.h"
+#include "zlib.h"
+#include "nsDependentString.h"
+#include <stdio.h>
+
+/**
+ * A simple class for writing to a .gz file.
+ *
+ * Note that the file that this interface produces has a different format than
+ * what you'd get if you compressed your data as a gzip stream and dumped the
+ * result to a file.
+ *
+ * The standard gunzip tool cannot decompress a raw gzip stream, but can handle
+ * the files produced by this interface.
+ */
+class nsGZFileWriter final {
+ virtual ~nsGZFileWriter();
+
+ public:
+ enum Operation { Append, Create };
+
+ explicit nsGZFileWriter(Operation aMode = Create);
+
+ NS_INLINE_DECL_REFCOUNTING(nsGZFileWriter)
+
+ /**
+ * Initialize this object. We'll write our gzip'ed data to the given file,
+ * overwriting its contents if the file exists.
+ *
+ * Init() will return an error if called twice. It's an error to call any
+ * other method on this interface without first calling Init().
+ */
+ [[nodiscard]] nsresult Init(nsIFile* aFile);
+
+ /**
+ * Alternate version of Init() for use when the file is already opened;
+ * e.g., with a FileDescriptor passed over IPC.
+ */
+ [[nodiscard]] nsresult InitANSIFileDesc(FILE* aFile);
+
+ /**
+ * Write the given string to the file.
+ */
+ [[nodiscard]] nsresult Write(const nsACString& aStr);
+
+ /**
+ * Write |length| bytes of |str| to the file.
+ */
+ [[nodiscard]] nsresult Write(const char* aStr, uint32_t aLen) {
+ return Write(nsDependentCSubstring(aStr, aLen));
+ }
+
+ /**
+ * Close this nsGZFileWriter. This method will be run when the underlying
+ * object is destroyed, so it's not strictly necessary to explicitly call it
+ * from your code.
+ *
+ * It's an error to call this method twice, and it's an error to call Write()
+ * after Finish() has been called.
+ */
+ nsresult Finish();
+
+ private:
+ Operation mMode;
+ bool mInitialized;
+ bool mFinished;
+ gzFile mGZFile;
+};
+
+#endif
diff --git a/xpcom/base/nsIAvailableMemoryWatcherBase.idl b/xpcom/base/nsIAvailableMemoryWatcherBase.idl
new file mode 100644
index 0000000000..07d48e8571
--- /dev/null
+++ b/xpcom/base/nsIAvailableMemoryWatcherBase.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsITabUnloader: interface to represent TabUnloader
+ *
+ * nsIAvailableMemoryWatcherBase: interface to watch the system's memory
+ * status and invoke a registered TabUnloader when it detected a low-memory
+ * and high-memory situation. The logic to detect such a memory situation
+ * is defined per platform.
+ */
+
+[scriptable, uuid(2e530956-6054-464f-9f4c-0ae6f8de5523)]
+interface nsITabUnloader : nsISupports
+{
+ /*
+ * Unload the least-recently-used tab.
+ * JS implementation of this interface TabUnloader.unloadTabAsync takes
+ * one parameter that defines a threshold to exclude fresh tabs from the
+ * unloading candidate tabs. Currently the memory watcher is the only one
+ * caller of this interface and it always expects the default threshold,
+ * so this interface takes no parameter.
+ */
+ void unloadTabAsync();
+};
+
+[scriptable, uuid(b0b5701e-239d-49db-9009-37e89f86441c)]
+interface nsIAvailableMemoryWatcherBase : nsISupports
+{
+ void registerTabUnloader(in nsITabUnloader aTabUnloader);
+ void onUnloadAttemptCompleted(in nsresult aResult);
+};
diff --git a/xpcom/base/nsIClassInfoImpl.h b/xpcom/base/nsIClassInfoImpl.h
new file mode 100644
index 0000000000..045e75e5af
--- /dev/null
+++ b/xpcom/base/nsIClassInfoImpl.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIClassInfoImpl_h__
+#define nsIClassInfoImpl_h__
+
+#include "mozilla/Alignment.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MacroArgs.h"
+#include "mozilla/MacroForEach.h"
+#include "nsIClassInfo.h"
+#include "nsISupportsImpl.h"
+
+#include <new>
+
+/**
+ * This header file provides macros which help you make your class implement
+ * nsIClassInfo. Implementing nsIClassInfo is particularly helpful if you have
+ * a C++ class which implements multiple interfaces and which you access from
+ * JavaScript. If that class implements nsIClassInfo, the JavaScript code
+ * won't have to call QueryInterface on instances of the class; all methods
+ * from all interfaces returned by GetInterfaces() will be available
+ * automagically.
+ *
+ * Here's all you need to do. Given a class
+ *
+ * class nsFooBar : public nsIFoo, public nsIBar { };
+ *
+ * you should already have the following nsISupports implementation in its cpp
+ * file:
+ *
+ * NS_IMPL_ISUPPORTS(nsFooBar, nsIFoo, nsIBar).
+ *
+ * Change this to
+ *
+ * NS_IMPL_CLASSINFO(nsFooBar, nullptr, 0, NS_FOOBAR_CID)
+ * NS_IMPL_ISUPPORTS_CI(nsFooBar, nsIFoo, nsIBar)
+ *
+ * If nsFooBar is threadsafe, change the 0 above to nsIClassInfo::THREADSAFE.
+ * If it's a singleton, use nsIClassInfo::SINGLETON. The full list of flags is
+ * in nsIClassInfo.idl.
+ *
+ * The nullptr parameter is there so you can pass a function for converting
+ * from an XPCOM object to a scriptable helper. Unless you're doing
+ * specialized JS work, you can probably leave this as nullptr.
+ *
+ * This file also defines the NS_IMPL_QUERY_INTERFACE_CI macro, which you can
+ * use to replace NS_IMPL_QUERY_INTERFACE, if you use that instead of
+ * NS_IMPL_ISUPPORTS.
+ *
+ * That's it! The rest is gory details.
+ *
+ *
+ * Notice that nsFooBar didn't need to inherit from nsIClassInfo in order to
+ * "implement" it. However, after adding these macros to nsFooBar, you you can
+ * QueryInterface an instance of nsFooBar to nsIClassInfo. How can this be?
+ *
+ * The answer lies in the NS_IMPL_ISUPPORTS_CI macro. It modifies nsFooBar's
+ * QueryInterface implementation such that, if we ask to QI to nsIClassInfo, it
+ * returns a singleton object associated with the class. (That singleton is
+ * defined by NS_IMPL_CLASSINFO.) So all nsFooBar instances will return the
+ * same object when QI'ed to nsIClassInfo. (You can see this in
+ * NS_IMPL_QUERY_CLASSINFO below.)
+ *
+ * This hack breaks XPCOM's rules, since if you take an instance of nsFooBar,
+ * QI it to nsIClassInfo, and then try to QI to nsIFoo, that will fail. On the
+ * upside, implementing nsIClassInfo doesn't add a vtable pointer to instances
+ * of your class.
+ *
+ * In principal, you can also implement nsIClassInfo by inheriting from the
+ * interface. But some code expects that when it QI's an object to
+ * nsIClassInfo, it gets back a singleton which isn't attached to any
+ * particular object. If a class were to implement nsIClassInfo through
+ * inheritance, that code might QI to nsIClassInfo and keep the resulting
+ * object alive, thinking it was only keeping alive the classinfo singleton,
+ * but in fact keeping a whole instance of the class alive. See, e.g., bug
+ * 658632.
+ *
+ * Unless you specifically need to have a different nsIClassInfo instance for
+ * each instance of your class, you should probably just implement nsIClassInfo
+ * as a singleton.
+ */
+
+class GenericClassInfo : public nsIClassInfo {
+ public:
+ struct ClassInfoData {
+ // This function pointer uses NS_CALLBACK_ because it's always set to an
+ // NS_IMETHOD function, which uses __stdcall on Win32.
+ typedef NS_CALLBACK_(nsresult, GetInterfacesProc)(nsTArray<nsIID>& aArray);
+ GetInterfacesProc getinterfaces;
+
+ // This function pointer doesn't use NS_CALLBACK_ because it's always set to
+ // a vanilla function.
+ typedef nsresult (*GetScriptableHelperProc)(nsIXPCScriptable** aHelper);
+ GetScriptableHelperProc getscriptablehelper;
+
+ uint32_t flags;
+ nsCID cid;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICLASSINFO
+
+ explicit GenericClassInfo(const ClassInfoData* aData) : mData(aData) {}
+
+ private:
+ const ClassInfoData* mData;
+};
+
+#define NS_CLASSINFO_NAME(_class) g##_class##_classInfoGlobal
+#define NS_CI_INTERFACE_GETTER_NAME(_class) _class##_GetInterfacesHelper
+#define NS_DECL_CI_INTERFACE_GETTER(_class) \
+ extern NS_IMETHODIMP NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray<nsIID> & \
+ array);
+
+#define NS_IMPL_CLASSINFO(_class, _getscriptablehelper, _flags, _cid) \
+ NS_DECL_CI_INTERFACE_GETTER(_class) \
+ static const GenericClassInfo::ClassInfoData k##_class##ClassInfoData = { \
+ NS_CI_INTERFACE_GETTER_NAME(_class), \
+ _getscriptablehelper, \
+ _flags | nsIClassInfo::SINGLETON_CLASSINFO, \
+ _cid, \
+ }; \
+ mozilla::AlignedStorage2<GenericClassInfo> k##_class##ClassInfoDataPlace; \
+ nsIClassInfo* NS_CLASSINFO_NAME(_class) = nullptr;
+
+#define NS_IMPL_QUERY_CLASSINFO(_class) \
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \
+ if (!NS_CLASSINFO_NAME(_class)) \
+ NS_CLASSINFO_NAME(_class) = new (k##_class##ClassInfoDataPlace.addr()) \
+ GenericClassInfo(&k##_class##ClassInfoData); \
+ foundInterface = NS_CLASSINFO_NAME(_class); \
+ } else
+
+#define NS_CLASSINFO_HELPER_BEGIN(_class, _c) \
+ NS_IMETHODIMP \
+ NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray<nsIID> & array) { \
+ array.Clear(); \
+ array.SetCapacity(_c);
+
+#define NS_CLASSINFO_HELPER_ENTRY(_interface) \
+ array.AppendElement(NS_GET_IID(_interface));
+
+#define NS_CLASSINFO_HELPER_END \
+ return NS_OK; \
+ }
+
+#define NS_IMPL_CI_INTERFACE_GETTER(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments to NS_IMPL_CI_INTERFACE_GETTER"); \
+ NS_CLASSINFO_HELPER_BEGIN(aClass, MOZ_ARG_COUNT(__VA_ARGS__)) \
+ MOZ_FOR_EACH(NS_CLASSINFO_HELPER_ENTRY, (), (__VA_ARGS__)) \
+ NS_CLASSINFO_HELPER_END
+
+#define NS_IMPL_CI_INTERFACE_GETTER0(aClass) \
+ NS_CLASSINFO_HELPER_BEGIN(aClass, 0) \
+ NS_CLASSINFO_HELPER_END
+
+// Note that this macro is an internal implementation of this header and
+// should not be used outside it. It does not end the interface map as this
+// is done in NS_IMPL_QUERY_INTERFACE_CI or the _INHERITED variant.
+#define NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments to NS_IMPL_QUERY_INTERFACE_CI"); \
+ NS_INTERFACE_MAP_BEGIN(aClass) \
+ MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, MOZ_ARG_1(__VA_ARGS__)) \
+ NS_IMPL_QUERY_CLASSINFO(aClass)
+
+#define NS_IMPL_QUERY_INTERFACE_CI(aClass, ...) \
+ NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \
+ NS_INTERFACE_MAP_END
+
+#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED(aClass, aSuper, ...) \
+ NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \
+ NS_INTERFACE_MAP_END_INHERITING \
+ (aSuper)
+
+#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(aClass, aSuper) \
+ NS_INTERFACE_MAP_BEGIN(aClass) \
+ NS_IMPL_QUERY_CLASSINFO(aClass) \
+ NS_INTERFACE_MAP_END_INHERITING(aSuper)
+
+#define NS_IMPL_ISUPPORTS_CI(aClass, ...) \
+ NS_IMPL_ADDREF(aClass) \
+ NS_IMPL_RELEASE(aClass) \
+ NS_IMPL_QUERY_INTERFACE_CI(aClass, __VA_ARGS__) \
+ NS_IMPL_CI_INTERFACE_GETTER(aClass, __VA_ARGS__)
+
+#endif // nsIClassInfoImpl_h__
diff --git a/xpcom/base/nsIConsoleListener.idl b/xpcom/base/nsIConsoleListener.idl
new file mode 100644
index 0000000000..45e6fcb1fc
--- /dev/null
+++ b/xpcom/base/nsIConsoleListener.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Used by the console service to notify listeners of new console messages.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIConsoleMessage;
+
+[scriptable, function, uuid(35c400a4-5792-438c-b915-65e30d58d557)]
+interface nsIConsoleListener : nsISupports
+{
+ void observe(in nsIConsoleMessage aMessage);
+};
diff --git a/xpcom/base/nsIConsoleMessage.idl b/xpcom/base/nsIConsoleMessage.idl
new file mode 100644
index 0000000000..ec9b901aaa
--- /dev/null
+++ b/xpcom/base/nsIConsoleMessage.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This is intended as a base interface; implementations may want to
+ * provide an object that can be qi'ed to provide more specific
+ * message information.
+ */
+[scriptable, uuid(3aba9617-10e2-4839-83ae-2e6fc4df428b)]
+interface nsIConsoleMessage : nsISupports
+{
+ /** Log level constants. */
+ const uint32_t debug = 0;
+ const uint32_t info = 1;
+ const uint32_t warn = 2;
+ const uint32_t error = 3;
+
+ /**
+ * The log level of this message.
+ */
+ readonly attribute uint32_t logLevel;
+
+ /**
+ * The time (in milliseconds from the Epoch) that the message instance
+ * was initialised.
+ * The timestamp is initialized as JS_now/1000 so that it can be
+ * compared to Date.now in Javascript.
+ */
+ readonly attribute long long timeStamp;
+
+ /**
+ * The time (in microseconds from the Epoch) that the message instance
+ * was initialised.
+ * The timestamp is initialized as JS_now.
+ */
+ readonly attribute long long microSecondTimeStamp;
+
+ [binaryname(MessageMoz)] readonly attribute AString message;
+
+ attribute boolean isForwardedFromContentProcess;
+
+ AUTF8String toString();
+};
+
+%{ C++
+#define NS_CONSOLEMESSAGE_CID \
+{ 0x024efc9e, 0x54dc, 0x4844, { 0x80, 0x4b, 0x41, 0xd3, 0xf3, 0x69, 0x90, 0x73 }}
+%}
diff --git a/xpcom/base/nsIConsoleService.idl b/xpcom/base/nsIConsoleService.idl
new file mode 100644
index 0000000000..0d20b678d0
--- /dev/null
+++ b/xpcom/base/nsIConsoleService.idl
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIConsoleListener;
+interface nsIConsoleMessage;
+
+[scriptable, builtinclass, uuid(0eb81d20-c37e-42d4-82a8-ca9ae96bdf52)]
+interface nsIConsoleService : nsISupports
+{
+ void logMessage(in nsIConsoleMessage message);
+
+ // This helper function executes `func` and redirects any exception
+ // that may be thrown while running it to the DevTools Console currently
+ // debugging `targetGlobal`.
+ //
+ // This helps flag the nsIScriptError with a particular innerWindowID
+ // which is especially useful for WebExtension content scripts
+ // where script are running in a Sandbox whose prototype is the content window.
+ // We expect content script exception to be flaged with the content window
+ // innerWindowID in order to appear in the tab's DevTools.
+ [implicit_jscontext]
+ jsval callFunctionAndLogException(in jsval targetGlobal, in jsval func);
+
+ // This is a variant of LogMessage which allows the caller to determine
+ // if the message should be output to an OS-specific log. This is used on
+ // B2G to control whether the message is logged to the android log or not.
+ cenum OutputMode : 8 {
+ SuppressLog = 0,
+ OutputToLog
+ };
+ void logMessageWithMode(in nsIConsoleMessage message,
+ in nsIConsoleService_OutputMode mode);
+
+ /**
+ * Convenience method for logging simple messages.
+ */
+ void logStringMessage(in wstring message);
+
+ /**
+ * Get an array of all the messages logged so far.
+ */
+ Array<nsIConsoleMessage> getMessageArray();
+
+ /**
+ * To guard against stack overflows from listeners that could log
+ * messages (it's easy to do this inadvertently from listeners
+ * implemented in JavaScript), we don't call any listeners when
+ * another error is already being logged.
+ */
+ void registerListener(in nsIConsoleListener listener);
+
+ /**
+ * Each registered listener should also be unregistered.
+ */
+ void unregisterListener(in nsIConsoleListener listener);
+
+ /**
+ * Clear the message buffer (e.g. for privacy reasons).
+ */
+ void reset();
+
+ /**
+ * Clear the message buffer for a given window.
+ */
+ void resetWindow(in uint64_t windowInnerId);
+};
+
+
+%{ C++
+#define NS_CONSOLESERVICE_CID \
+{ 0x7e3ff85c, 0x1dd2, 0x11b2, { 0x8d, 0x4b, 0xeb, 0x45, 0x2c, 0xb0, 0xff, 0x40 }}
+
+#define NS_CONSOLESERVICE_CONTRACTID "@mozilla.org/consoleservice;1"
+%}
diff --git a/xpcom/base/nsICycleCollectorListener.idl b/xpcom/base/nsICycleCollectorListener.idl
new file mode 100644
index 0000000000..5e0b059a13
--- /dev/null
+++ b/xpcom/base/nsICycleCollectorListener.idl
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include <stdio.h>
+
+class nsCycleCollectorLogger;
+%}
+
+[ptr] native FILE(FILE);
+[ptr] native nsCycleCollectorLoggerPtr(nsCycleCollectorLogger);
+interface nsIFile;
+
+/**
+ * A set of interfaces for recording the cycle collector's work. An instance
+ * of nsICycleCollectorListener can be configured to enable various
+ * options, then passed to the cycle collector when it runs.
+ * Note that additional logging options are available by setting environment
+ * variables, as described at the top of nsCycleCollector.cpp.
+ */
+
+/**
+ * nsICycleCollectorHandler is the interface JS code should implement to
+ * receive the results logged by an nsICycleCollectorListener
+ * instance. Pass an instance of this to the logger's 'processNext' method
+ * after the collection has run. This will describe the objects the cycle
+ * collector visited, the edges it found, and the conclusions it reached
+ * about the liveness of objects.
+ *
+ * In more detail:
+ * - For each node in the graph:
+ * - a call is made to either |noteRefCountedObject| or |noteGCedObject|, to
+ * describe the node itself; and
+ * - for each edge starting at that node, a call is made to |noteEdge|.
+ *
+ * - Then, a series of calls are made to:
+ * - |describeRoot|, for reference-counted nodes that the CC has identified as
+ * being alive because there are unknown references to those nodes.
+ * - |describeGarbage|, for nodes the cycle collector has identified as garbage.
+ *
+ * Any node not mentioned in a call to |describeRoot| or |describeGarbage| is
+ * neither a root nor garbage. The cycle collector was able to find all of the
+ * edges implied by the node's reference count.
+ */
+[scriptable, uuid(7f093367-1492-4b89-87af-c01dbc831246)]
+interface nsICycleCollectorHandler : nsISupports
+{
+ void noteRefCountedObject(in ACString aAddress,
+ in unsigned long aRefCount,
+ in ACString aObjectDescription);
+ void noteGCedObject(in ACString aAddress,
+ in boolean aMarked,
+ in ACString aObjectDescription,
+ in ACString aCompartmentAddress);
+ void noteEdge(in ACString aFromAddress,
+ in ACString aToAddress,
+ in ACString aEdgeName);
+ void describeRoot(in ACString aAddress,
+ in unsigned long aKnownEdges);
+ void describeGarbage(in ACString aAddress);
+};
+
+
+/**
+ * This interface allows replacing the log-writing backend for an
+ * nsICycleCollectorListener. As this interface is also called while
+ * the cycle collector is running, it cannot be implemented in JS.
+ */
+[scriptable, builtinclass, uuid(3ad9875f-d0e4-4ac2-87e3-f127f6c02ce1)]
+interface nsICycleCollectorLogSink : nsISupports
+{
+ [noscript] void open(out FILE aGCLog, out FILE aCCLog);
+ void closeGCLog();
+ void closeCCLog();
+
+ // This string will appear somewhere in the log's filename.
+ attribute AString filenameIdentifier;
+
+ // This is the process ID; it can be changed if logging is on behalf
+ // of another process.
+ attribute int32_t processIdentifier;
+
+ // The GC log file, if logging to files.
+ readonly attribute nsIFile gcLog;
+
+ // The CC log file, if logging to files.
+ readonly attribute nsIFile ccLog;
+};
+
+
+/**
+ * This interface is used to configure some reporting options for the cycle
+ * collector. This interface cannot be implemented by JavaScript code, as it
+ * is called while the cycle collector is running.
+ *
+ * To analyze cycle collection data in JS:
+ *
+ * - Create an instance of nsICycleCollectorListener, which implements this
+ * interface. In C++, this can be done by calling
+ * nsCycleCollector_createLogger(). In JS, this can be done by calling
+ * Components.utils.createCCLogger().
+ *
+ * - Set its |disableLog| property to true. This prevents the logger from
+ * printing messages about each method call to a temporary log file.
+ *
+ * - Set its |wantAfterProcessing| property to true. This tells the logger
+ * to record calls to its methods in memory. The |processNext| method
+ * returns events from this record.
+ *
+ * - Perform a collection using the logger. For example, call
+ * |nsIDOMWindowUtils|'s |garbageCollect| method, passing the logger as
+ * the |aListener| argument.
+ *
+ * - When the collection is complete, loop calling the logger's
+ * |processNext| method, passing a JavaScript object that implements
+ * nsICycleCollectorHandler. This JS code is free to allocate and operate
+ * on objects however it pleases: the cycle collector has finished its
+ * work, and the JS code is simply consuming recorded data.
+ */
+[scriptable, builtinclass, uuid(703b53b6-24f6-40c6-9ea9-aeb2dc53d170)]
+interface nsICycleCollectorListener : nsISupports
+{
+ // Return a listener that directs the cycle collector to traverse
+ // objects that it knows won't be collectable.
+ //
+ // Note that even this listener will not visit every node in the heap;
+ // the cycle collector can't see the entire heap. But while this
+ // listener is in use, the collector disables some optimizations it
+ // normally uses to avoid certain classes of objects that are certainly
+ // alive. So, if your purpose is to get a view of the portion of the
+ // heap that is of interest to the cycle collector, and not simply find
+ // garbage, then you should use the listener this returns.
+ //
+ // Note that this does not necessarily return a new listener; rather, it may
+ // simply set a flag on this listener (a side effect!) and return it.
+ nsICycleCollectorListener allTraces();
+
+ // True if this listener will behave like one returned by allTraces().
+ readonly attribute boolean wantAllTraces;
+
+ // If true, do not log each method call to a temporary file.
+ // Initially false.
+ attribute boolean disableLog;
+
+ // If |disableLog| is false, this object will be sent the log text.
+ attribute nsICycleCollectorLogSink logSink;
+
+ // If true, record all method calls in memory, to be retrieved later
+ // using |processNext|. Initially false.
+ attribute boolean wantAfterProcessing;
+
+ // Report the next recorded event to |aHandler|, and remove it from the
+ // record. Return false if there isn't anything more to process.
+ //
+ // Note that we only record events to report here if our
+ // |wantAfterProcessing| property is true.
+ boolean processNext(in nsICycleCollectorHandler aHandler);
+
+ // Return the current object as an nsCycleCollectorLogger*, which is the
+ // only class that should be implementing this interface. We need the
+ // concrete implementation type to help the GC rooting analysis.
+ [noscript] nsCycleCollectorLoggerPtr asLogger();
+};
diff --git a/xpcom/base/nsID.cpp b/xpcom/base/nsID.cpp
new file mode 100644
index 0000000000..701bf647d1
--- /dev/null
+++ b/xpcom/base/nsID.cpp
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsID.h"
+
+#include <limits.h>
+
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RandomNum.h"
+#include "mozilla/Sprintf.h"
+#include "nss.h"
+#include "ScopedNSSTypes.h"
+
+[[nodiscard]] static bool GenerateRandomBytesFromNSS(void* aBuffer,
+ size_t aLength) {
+ MOZ_ASSERT(aBuffer);
+
+ // Bounds check that we can safely truncate size_t `aLength` to an int.
+ if (aLength == 0 || aLength > INT_MAX) {
+ MOZ_ASSERT_UNREACHABLE("Bad aLength");
+ return false;
+ }
+ int len = static_cast<int>(aLength);
+
+ // Only try to use NSS on the main thread.
+ if (!NS_IsMainThread() || !NSS_IsInitialized()) {
+ return false;
+ }
+
+ mozilla::UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ MOZ_ASSERT_UNREACHABLE("Null slot");
+ return false;
+ }
+
+ SECStatus srv = PK11_GenerateRandomOnSlot(
+ slot.get(), static_cast<unsigned char*>(aBuffer), len);
+ MOZ_ASSERT(srv == SECSuccess);
+ return (srv == SECSuccess);
+}
+
+nsresult nsID::GenerateUUIDInPlace(nsID& aId) {
+ // Firefox needs to generate some UUIDs before NSS has been initialized. We
+ // prefer NSS's RNG, but if NSS is not available yet or returns an error, fall
+ // back to MFBT's GenerateRandomBytes().
+ if (!GenerateRandomBytesFromNSS(&aId, sizeof(nsID)) &&
+ !mozilla::GenerateRandomBytesFromOS(&aId, sizeof(nsID))) {
+ MOZ_ASSERT_UNREACHABLE("GenerateRandomBytesFromOS() failed");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Put in the version
+ aId.m2 &= 0x0fff;
+ aId.m2 |= 0x4000;
+
+ // Put in the variant
+ aId.m3[0] &= 0x3f;
+ aId.m3[0] |= 0x80;
+
+ return NS_OK;
+}
+
+nsID nsID::GenerateUUID() {
+ nsID uuid;
+ nsresult rv = GenerateUUIDInPlace(uuid);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ return uuid;
+}
+
+void nsID::Clear() {
+ m0 = 0;
+ m1 = 0;
+ m2 = 0;
+ memset(m3, 0, sizeof(m3));
+}
+
+/**
+ * Multiplies the_int_var with 16 (0x10) and adds the value of the
+ * hexadecimal digit the_char. If it fails it returns false from
+ * the function it's used in.
+ */
+
+#define ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(the_char, the_int_var) \
+ the_int_var = (the_int_var << 4) + the_char; \
+ if (the_char >= '0' && the_char <= '9') \
+ the_int_var -= '0'; \
+ else if (the_char >= 'a' && the_char <= 'f') \
+ the_int_var -= 'a' - 10; \
+ else if (the_char >= 'A' && the_char <= 'F') \
+ the_int_var -= 'A' - 10; \
+ else \
+ return false
+
+/**
+ * Parses number_of_chars characters from the char_pointer pointer and
+ * puts the number in the dest_variable. The pointer is moved to point
+ * at the first character after the parsed ones. If it fails it returns
+ * false from the function the macro is used in.
+ */
+
+#define PARSE_CHARS_TO_NUM(char_pointer, dest_variable, number_of_chars) \
+ do { \
+ int32_t _i = number_of_chars; \
+ dest_variable = 0; \
+ while (_i) { \
+ ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(*char_pointer, dest_variable); \
+ char_pointer++; \
+ _i--; \
+ } \
+ } while (0)
+
+/**
+ * Parses a hyphen from the char_pointer string. If there is no hyphen there
+ * the function returns false from the function it's used in. The
+ * char_pointer is advanced one step.
+ */
+
+#define PARSE_HYPHEN(char_pointer) \
+ if (*(char_pointer++) != '-') return false
+
+/*
+ * Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} string into
+ * an nsID. It can also handle the old format without the { and }.
+ */
+
+bool nsID::Parse(const char* aIDStr) {
+ /* Optimized for speed */
+ if (!aIDStr) {
+ return false;
+ }
+
+ bool expectFormat1 = (aIDStr[0] == '{');
+ if (expectFormat1) {
+ ++aIDStr;
+ }
+
+ PARSE_CHARS_TO_NUM(aIDStr, m0, 8);
+ PARSE_HYPHEN(aIDStr);
+ PARSE_CHARS_TO_NUM(aIDStr, m1, 4);
+ PARSE_HYPHEN(aIDStr);
+ PARSE_CHARS_TO_NUM(aIDStr, m2, 4);
+ PARSE_HYPHEN(aIDStr);
+ int i;
+ for (i = 0; i < 2; ++i) {
+ PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2);
+ }
+ PARSE_HYPHEN(aIDStr);
+ while (i < 8) {
+ PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2);
+ i++;
+ }
+
+ return expectFormat1 ? *aIDStr == '}' : true;
+}
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+
+/*
+ * Returns a managed string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
+ * format.
+ */
+nsIDToCString nsID::ToString() const { return nsIDToCString(*this); }
+
+static const char sHexChars[256 * 2 + 1] =
+ "000102030405060708090a0b0c0d0e0f"
+ "101112131415161718191a1b1c1d1e1f"
+ "202122232425262728292a2b2c2d2e2f"
+ "303132333435363738393a3b3c3d3e3f"
+ "404142434445464748494a4b4c4d4e4f"
+ "505152535455565758595a5b5c5d5e5f"
+ "606162636465666768696a6b6c6d6e6f"
+ "707172737475767778797a7b7c7d7e7f"
+ "808182838485868788898a8b8c8d8e8f"
+ "909192939495969798999a9b9c9d9e9f"
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
+
+// Writes a zero-padded 8-bit integer as lowercase hex into aDest[0..2]
+static void ToHex8Bit(uint8_t aValue, char* aDest) {
+ aDest[0] = sHexChars[2 * aValue];
+ aDest[1] = sHexChars[2 * aValue + 1];
+}
+
+// Writes a zero-padded 16-bit integer as lowercase hex into aDest[0..4]
+static void ToHex16Bit(uint16_t aValue, char* aDest) {
+ const uint8_t hi = (aValue >> 8);
+ const uint8_t lo = aValue;
+ ToHex8Bit(hi, &aDest[0]);
+ ToHex8Bit(lo, &aDest[2]);
+}
+
+// Writes a zero-padded 32-bit integer as lowercase hex into aDest[0..8]
+static void ToHex32Bit(uint32_t aValue, char* aDest) {
+ const uint16_t hi = (aValue >> 16);
+ const uint16_t lo = aValue;
+ ToHex16Bit(hi, &aDest[0]);
+ ToHex16Bit(lo, &aDest[4]);
+}
+
+void nsID::ToProvidedString(char (&aDest)[NSID_LENGTH]) const {
+ // Stringify manually, for best performance.
+ //
+ // "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}"
+ // "{ecc35fd2-9029-4287-a826-b0a241d48d31}"
+ aDest[0] = '{';
+ ToHex32Bit(m0, &aDest[1]);
+ aDest[9] = '-';
+ ToHex16Bit(m1, &aDest[10]);
+ aDest[14] = '-';
+ ToHex16Bit(m2, &aDest[15]);
+ aDest[19] = '-';
+ ToHex8Bit(m3[0], &aDest[20]);
+ ToHex8Bit(m3[1], &aDest[22]);
+ aDest[24] = '-';
+ ToHex8Bit(m3[2], &aDest[25]);
+ ToHex8Bit(m3[3], &aDest[27]);
+ ToHex8Bit(m3[4], &aDest[29]);
+ ToHex8Bit(m3[5], &aDest[31]);
+ ToHex8Bit(m3[6], &aDest[33]);
+ ToHex8Bit(m3[7], &aDest[35]);
+ aDest[37] = '}';
+ aDest[38] = '\0';
+}
+
+#endif // XPCOM_GLUE_AVOID_NSPR
+
+nsID* nsID::Clone() const {
+ auto id = static_cast<nsID*>(moz_xmalloc(sizeof(nsID)));
+ *id = *this;
+ return id;
+}
diff --git a/xpcom/base/nsID.h b/xpcom/base/nsID.h
new file mode 100644
index 0000000000..c7feddd1cc
--- /dev/null
+++ b/xpcom/base/nsID.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsID_h__
+#define nsID_h__
+
+#include <string.h>
+
+#include "nscore.h"
+
+#define NSID_LENGTH 39
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+class nsIDToCString;
+#endif
+
+/**
+ * A "unique identifier". This is modeled after OSF DCE UUIDs.
+ */
+
+struct nsID {
+ uint32_t m0;
+ uint16_t m1;
+ uint16_t m2;
+ uint8_t m3[8];
+
+ /**
+ * Create a new random UUID.
+ * GenerateUUIDInPlace() is fallible, whereas GenerateUUID() will abort in
+ * the unlikely case that the OS RNG returns an error.
+ */
+ [[nodiscard]] static nsresult GenerateUUIDInPlace(nsID& aId);
+ static nsID GenerateUUID();
+
+ /**
+ * Ensures everything is zeroed out.
+ */
+ void Clear();
+
+ /**
+ * Equivalency method. Compares this nsID with another.
+ * @return true if they are the same, false if not.
+ */
+
+ inline bool Equals(const nsID& aOther) const {
+ // At the time of this writing, modern compilers (namely Clang) inline this
+ // memcmp call into a single SIMD operation on x86/x86-64 architectures (as
+ // long as we compare to zero rather than returning the full integer return
+ // value), which is measurably more efficient to any manual comparisons we
+ // can do directly.
+#if defined(__x86_64__) || defined(__i386__)
+ return !memcmp(this, &aOther, sizeof *this);
+#else
+ // However, on ARM architectures, compilers still tend to generate a direct
+ // memcmp call, which we'd like to avoid.
+ return (((uint32_t*)&m0)[0] == ((uint32_t*)&aOther.m0)[0]) &&
+ (((uint32_t*)&m0)[1] == ((uint32_t*)&aOther.m0)[1]) &&
+ (((uint32_t*)&m0)[2] == ((uint32_t*)&aOther.m0)[2]) &&
+ (((uint32_t*)&m0)[3] == ((uint32_t*)&aOther.m0)[3]);
+#endif
+ }
+
+ inline bool operator==(const nsID& aOther) const { return Equals(aOther); }
+
+ /**
+ * nsID Parsing method. Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
+ * string into an nsID
+ */
+ bool Parse(const char* aIDStr);
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+ /**
+ * nsID string encoder. Returns a managed string in
+ * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format.
+ */
+ nsIDToCString ToString() const;
+
+ /**
+ * nsID string encoder. Builds a string in
+ * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format, into a char[NSID_LENGTH]
+ * buffer provided by the caller (for instance, on the stack).
+ */
+ void ToProvidedString(char (&aDest)[NSID_LENGTH]) const;
+
+#endif // XPCOM_GLUE_AVOID_NSPR
+
+ // Infallibly duplicate an nsID. Must be freed with free().
+ nsID* Clone() const;
+};
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+/**
+ * A stack helper class to convert a nsID to a string. Useful
+ * for printing nsIDs. For example:
+ * nsID aID = ...;
+ * printf("%s", nsIDToCString(aID).get());
+ */
+class nsIDToCString {
+ public:
+ explicit nsIDToCString(const nsID& aID) {
+ aID.ToProvidedString(mStringBytes);
+ }
+
+ const char* get() const { return mStringBytes; }
+
+ protected:
+ char mStringBytes[NSID_LENGTH];
+};
+#endif
+
+/*
+ * Class IDs
+ */
+
+typedef nsID nsCID;
+
+// Define an CID
+#define NS_DEFINE_CID(_name, _cidspec) const nsCID _name = _cidspec
+
+#define NS_DEFINE_NAMED_CID(_name) static const nsCID k##_name = _name
+
+#define REFNSCID const nsCID&
+
+/**
+ * An "interface id" which can be used to uniquely identify a given
+ * interface.
+ */
+
+typedef nsID nsIID;
+
+/**
+ * A macro shorthand for <tt>const nsIID&<tt>
+ */
+
+#define REFNSIID const nsIID&
+
+/**
+ * A macro to build the static const IID accessor method. The Dummy
+ * template parameter only exists so that the kIID symbol will be linked
+ * properly (weak symbol on linux, gnu_linkonce on mac, multiple-definitions
+ * merged on windows). Dummy should always be instantiated as "void".
+ */
+
+#define NS_DECLARE_STATIC_IID_ACCESSOR(the_iid) \
+ template <typename T, typename U> \
+ struct COMTypeInfo;
+
+#define NS_DEFINE_STATIC_IID_ACCESSOR(the_interface, the_iid) \
+ template <typename T> \
+ struct the_interface::COMTypeInfo<the_interface, T> { \
+ static const nsIID kIID NS_HIDDEN; \
+ }; \
+ template <typename T> \
+ const nsIID the_interface::COMTypeInfo<the_interface, T>::kIID NS_HIDDEN = \
+ the_iid;
+
+/**
+ * A macro to build the static const CID accessor method
+ */
+
+#define NS_DEFINE_STATIC_CID_ACCESSOR(the_cid) \
+ static const nsID& GetCID() { \
+ static const nsID cid = the_cid; \
+ return cid; \
+ }
+
+#define NS_GET_IID(T) (T::COMTypeInfo<T, void>::kIID)
+#define NS_GET_TEMPLATE_IID(T) (T::template COMTypeInfo<T, void>::kIID)
+
+#endif
diff --git a/xpcom/base/nsIDUtils.h b/xpcom/base/nsIDUtils.h
new file mode 100644
index 0000000000..ffb9d02f19
--- /dev/null
+++ b/xpcom/base/nsIDUtils.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCOM_BASE_NSIDUTILS_H_
+#define XPCOM_BASE_NSIDUTILS_H_
+
+#include "nsID.h"
+#include "nsString.h"
+#include "nsTString.h"
+
+/**
+ * Trims the brackets around the nsID string, since it wraps its ID with
+ * brackets: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
+ */
+template <typename T>
+class NSID_TrimBrackets : public nsTAutoString<T> {
+ public:
+ explicit NSID_TrimBrackets(const nsID& aID) {
+ nsIDToCString toCString(aID);
+ // The string from nsIDToCString::get() includes a null terminator.
+ // Thus this trims 3 characters: `{`, `}`, and the null terminator.
+ this->AssignASCII(toCString.get() + 1, NSID_LENGTH - 3);
+ }
+};
+
+using NSID_TrimBracketsUTF16 = NSID_TrimBrackets<char16_t>;
+using NSID_TrimBracketsASCII = NSID_TrimBrackets<char>;
+
+#endif // XPCOM_BASE_NSIDUTILS_H_
diff --git a/xpcom/base/nsIDebug2.idl b/xpcom/base/nsIDebug2.idl
new file mode 100644
index 0000000000..4672fea7ea
--- /dev/null
+++ b/xpcom/base/nsIDebug2.idl
@@ -0,0 +1,100 @@
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* interface to expose information about calls to NS_DebugBreak */
+
+#include "nsISupports.idl"
+
+/**
+ * @note C/C++ consumers who are planning to use the nsIDebug2 interface with
+ * the "@mozilla.org/xpcom;1" contract should use NS_DebugBreak from xpcom
+ * glue instead.
+ *
+ */
+
+[builtinclass, scriptable, uuid(9641dc15-10fb-42e3-a285-18be90a5c10b)]
+interface nsIDebug2 : nsISupports
+{
+ /**
+ * Whether XPCOM was compiled with DEBUG defined. This often
+ * correlates to whether other code (e.g., Firefox, XULRunner) was
+ * compiled with DEBUG defined.
+ */
+ readonly attribute boolean isDebugBuild;
+
+ /**
+ * The number of assertions since process start.
+ */
+ readonly attribute long assertionCount;
+
+ /**
+ * Whether a debugger is currently attached.
+ * Supports Windows + Mac
+ */
+ readonly attribute bool isDebuggerAttached;
+
+ /**
+ * Show an assertion and trigger nsIDebug2.break().
+ *
+ * @param aStr assertion message
+ * @param aExpr expression that failed
+ * @param aFile file containing assertion
+ * @param aLine line number of assertion
+ */
+ void assertion(in string aStr,
+ in string aExpr,
+ in string aFile,
+ in long aLine);
+
+ /**
+ * Show a warning.
+ *
+ * @param aStr warning message
+ * @param aFile file containing assertion
+ * @param aLine line number of assertion
+ */
+ void warning(in string aStr,
+ in string aFile,
+ in long aLine);
+
+ /**
+ * Request to break into a debugger.
+ *
+ * @param aFile file containing break request
+ * @param aLine line number of break request
+ */
+ void break(in string aFile,
+ in long aLine);
+
+ /**
+ * Request the process to trigger a fatal abort.
+ *
+ * @param aFile file containing abort request
+ * @param aLine line number of abort request
+ */
+ void abort(in string aFile,
+ in long aLine);
+
+ /**
+ * Request the process to trigger a fatal panic!() from Rust code.
+ *
+ * @param aMessage the string to pass to panic!().
+ */
+ void rustPanic(in string aMessage);
+
+ /**
+ * Request the process to log a message for a target and level from Rust code.
+ *
+ * @param aTarget the string representing the log target.
+ * @param aMessage the string representing the log message.
+ */
+ void rustLog(in string aTarget,
+ in string aMessage);
+
+ /**
+ * Cause an Out of Memory Crash.
+ */
+ void crashWithOOM();
+};
diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl
new file mode 100644
index 0000000000..7f529e1c08
--- /dev/null
+++ b/xpcom/base/nsIException.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Interfaces for representing cross-language exceptions and stack traces.
+ */
+#include "nsISupports.idl"
+
+[ptr] native JSContext(JSContext);
+native StackFrameRef(already_AddRefed<nsIStackFrame>);
+
+[scriptable, builtinclass, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)]
+interface nsIStackFrame : nsISupports
+{
+ [implicit_jscontext, binaryname(FilenameXPCOM)]
+ readonly attribute AString filename;
+ [implicit_jscontext, binaryname(NameXPCOM)]
+ readonly attribute AString name;
+ // Unique identifier of the script source for the frame, or zero.
+ [implicit_jscontext, binaryname(SourceIdXPCOM)]
+ readonly attribute int32_t sourceId;
+ // Valid line numbers begin at '1'. '0' indicates unknown.
+ [implicit_jscontext, binaryname(LineNumberXPCOM)]
+ readonly attribute int32_t lineNumber;
+ [implicit_jscontext, binaryname(ColumnNumberXPCOM)]
+ readonly attribute int32_t columnNumber;
+ readonly attribute AUTF8String sourceLine;
+ [implicit_jscontext, binaryname(AsyncCauseXPCOM)]
+ readonly attribute AString asyncCause;
+ [implicit_jscontext, binaryname(AsyncCallerXPCOM)]
+ readonly attribute nsIStackFrame asyncCaller;
+ [implicit_jscontext, binaryname(CallerXPCOM)]
+ readonly attribute nsIStackFrame caller;
+
+ // Returns a formatted stack string that looks like the sort of
+ // string that would be returned by .stack on JS Error objects.
+ // Only works on JS-language stack frames.
+ [implicit_jscontext, binaryname(FormattedStackXPCOM)]
+ readonly attribute AString formattedStack;
+
+ // Returns the underlying SavedFrame object for native JavaScript stacks,
+ // or null if this is not a native JavaScript stack frame.
+ readonly attribute jsval nativeSavedFrame;
+
+ [implicit_jscontext, binaryname(ToStringXPCOM)]
+ AUTF8String toString();
+
+ // Infallible things to be called from C++.
+ [notxpcom, nostdcall]
+ void getFilename(in JSContext aCx, out AString aFilename);
+ [notxpcom, nostdcall]
+ void getName(in JSContext aCx, out AString aName);
+ [notxpcom, nostdcall]
+ int32_t getSourceId(in JSContext aCx);
+ [notxpcom, nostdcall]
+ int32_t getLineNumber(in JSContext aCx);
+ [notxpcom, nostdcall]
+ int32_t getColumnNumber(in JSContext aCx);
+ [notxpcom, nostdcall]
+ void getAsyncCause(in JSContext aCx, out AString aAsyncCause);
+ [notxpcom, nostdcall]
+ StackFrameRef getAsyncCaller(in JSContext aCx);
+ [notxpcom, nostdcall]
+ StackFrameRef getCaller(in JSContext aCx);
+ [notxpcom, nostdcall]
+ void getFormattedStack(in JSContext aCx, out AString aFormattedStack);
+ [notxpcom, nostdcall, binaryname(ToString)]
+ void toStringInfallible(in JSContext aCx, out AUTF8String aString);
+};
+
+// This interface only exists because of all the JS consumers doing
+// "instanceof Ci.nsIException". We should switch them to something else and
+// get rid of it; bug 1435856 tracks that. C++ code should NOT use this; use
+// mozilla::dom::Exception instead.
+[scriptable, builtinclass, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)]
+interface nsIException : nsISupports
+{
+};
diff --git a/xpcom/base/nsIInterfaceRequestor.idl b/xpcom/base/nsIInterfaceRequestor.idl
new file mode 100644
index 0000000000..e0c4c0d7bb
--- /dev/null
+++ b/xpcom/base/nsIInterfaceRequestor.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIInterfaceRequestor interface defines a generic interface for
+ * requesting interfaces that a given object might provide access to.
+ * This is very similar to QueryInterface found in nsISupports.
+ * The main difference is that interfaces returned from GetInterface()
+ * are not required to provide a way back to the object implementing this
+ * interface. The semantics of QI() dictate that given an interface A that
+ * you QI() on to get to interface B, you must be able to QI on B to get back
+ * to A. This interface however allows you to obtain an interface C from A
+ * that may or most likely will not have the ability to get back to A.
+ */
+
+[scriptable, uuid(033A1470-8B2A-11d3-AF88-00A024FFC08C)]
+interface nsIInterfaceRequestor : nsISupports
+{
+ /**
+ * Retrieves the specified interface pointer.
+ *
+ * @param uuid The IID of the interface being requested.
+ * @param result [out] The interface pointer to be filled in if
+ * the interface is accessible.
+ * @throws NS_NOINTERFACE - interface not accessible.
+ * @throws NS_ERROR* - method failure.
+ */
+ void getInterface(in nsIIDRef uuid,
+ [iid_is(uuid),retval] out nsQIResult result);
+};
diff --git a/xpcom/base/nsIInterfaceRequestorUtils.cpp b/xpcom/base/nsIInterfaceRequestorUtils.cpp
new file mode 100644
index 0000000000..44dd51921d
--- /dev/null
+++ b/xpcom/base/nsIInterfaceRequestorUtils.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+nsresult nsGetInterface::operator()(const nsIID& aIID,
+ void** aInstancePtr) const {
+ nsresult status;
+
+ if (mSource) {
+ nsCOMPtr<nsIInterfaceRequestor> factoryPtr = do_QueryInterface(mSource);
+ if (factoryPtr) {
+ status = factoryPtr->GetInterface(aIID, aInstancePtr);
+ } else {
+ status = NS_ERROR_NO_INTERFACE;
+ }
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ if (NS_FAILED(status)) {
+ *aInstancePtr = 0;
+ }
+ if (mErrorPtr) {
+ *mErrorPtr = status;
+ }
+ return status;
+}
diff --git a/xpcom/base/nsIInterfaceRequestorUtils.h b/xpcom/base/nsIInterfaceRequestorUtils.h
new file mode 100644
index 0000000000..c3116ea9f2
--- /dev/null
+++ b/xpcom/base/nsIInterfaceRequestorUtils.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsInterfaceRequestorUtils_h
+#define __nsInterfaceRequestorUtils_h
+
+#include "nsCOMPtr.h"
+
+// a type-safe shortcut for calling the |GetInterface()| member function
+// T must inherit from nsIInterfaceRequestor, but the cast may be ambiguous.
+template <class T, class DestinationType>
+inline nsresult CallGetInterface(T* aSource, DestinationType** aDestination) {
+ MOZ_ASSERT(aSource, "null parameter");
+ MOZ_ASSERT(aDestination, "null parameter");
+
+ return aSource->GetInterface(NS_GET_TEMPLATE_IID(DestinationType),
+ reinterpret_cast<void**>(aDestination));
+}
+
+class MOZ_STACK_CLASS nsGetInterface final : public nsCOMPtr_helper {
+ public:
+ nsGetInterface(nsISupports* aSource, nsresult* aError)
+ : mSource(aSource), mErrorPtr(aError) {}
+
+ virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override;
+
+ private:
+ nsISupports* MOZ_NON_OWNING_REF mSource;
+ nsresult* mErrorPtr;
+};
+
+inline const nsGetInterface do_GetInterface(nsISupports* aSource,
+ nsresult* aError = 0) {
+ return nsGetInterface(aSource, aError);
+}
+
+#endif // __nsInterfaceRequestorUtils_h
diff --git a/xpcom/base/nsIMacPreferencesReader.idl b/xpcom/base/nsIMacPreferencesReader.idl
new file mode 100644
index 0000000000..470fd92ec9
--- /dev/null
+++ b/xpcom/base/nsIMacPreferencesReader.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupportsPrimitives.idl"
+
+%{C++
+#define ENTERPRISE_POLICIES_ENABLED_KEY "EnterprisePoliciesEnabled"
+%}
+
+/**
+ * This interface is designed to provide scriptable access to the macOS
+ * preferences system.
+ *
+ * This interface is highly macOS specific.
+ */
+[builtinclass, scriptable, uuid(b0f20595-88ce-4738-a1a4-24de78eb8051)]
+interface nsIMacPreferencesReader : nsISupports
+{
+ /**
+ * This method checks whether macOS policies are enabled.
+ *
+ * @return true if macOS policies are enabled, false otherwise.
+ */
+ bool policiesEnabled();
+
+ /**
+ * This method reads and returns the macOS preferences.
+ *
+ * @return A JSON object containing all macOS preferences.
+ */
+ [implicit_jscontext]
+ jsval readPreferences();
+};
diff --git a/xpcom/base/nsIMemoryInfoDumper.idl b/xpcom/base/nsIMemoryInfoDumper.idl
new file mode 100644
index 0000000000..79b03a709d
--- /dev/null
+++ b/xpcom/base/nsIMemoryInfoDumper.idl
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsICycleCollectorLogSink;
+
+[scriptable, function, uuid(2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e)]
+interface nsIFinishDumpingCallback : nsISupports
+{
+ void callback(in nsISupports data);
+};
+
+/**
+ * Callback interface for |dumpGCAndCCLogsToFile|, below. Note that
+ * these method calls can occur before |dumpGCAndCCLogsToFile|
+ * returns.
+ */
+[scriptable, uuid(dc1b2b24-65bd-441b-b6bd-cb5825a7ed14)]
+interface nsIDumpGCAndCCLogsCallback : nsISupports
+{
+ /**
+ * Called whenever a process has successfully finished dumping its GC/CC logs.
+ * Incomplete dumps (e.g., if the child crashes or is killed due to memory
+ * exhaustion) are not reported.
+ *
+ * @param aGCLog The file that the GC log was written to.
+ *
+ * @param aCCLog The file that the CC log was written to.
+ *
+ * @param aIsParent indicates whether this log file pair is from the
+ * parent process.
+ */
+ void onDump(in nsIFile aGCLog,
+ in nsIFile aCCLog,
+ in bool aIsParent);
+
+ /**
+ * Called when GC/CC logging has finished, after all calls to |onDump|.
+ */
+ void onFinish();
+};
+
+[scriptable, builtinclass, uuid(48541b74-47ee-4a62-9557-7f4b809bda5c)]
+interface nsIMemoryInfoDumper : nsISupports
+{
+ /**
+ * This dumps gzipped memory reports for this process and its child
+ * processes. If a file of the given name exists, it will be overwritten.
+ *
+ * @param aFilename The output file.
+ *
+ * @param aFinishDumping The callback called on completion.
+ *
+ * @param aFinishDumpingData The environment for the callback.
+ *
+ * @param aAnonymize Should the reports be anonymized?
+ *
+ * @param aMinimizeMemoryUsage indicates whether we should run a series of
+ * GC/CC's in an attempt to reduce our memory usage before collecting our
+ * memory report.
+ *
+ * Sample output, annotated with comments for explanatory purposes.
+ *
+ * {
+ * // The version number of the format, which will be incremented each time
+ * // backwards-incompatible changes are made. A mandatory integer.
+ * "version": 1
+ *
+ * // Equal to nsIMemoryReporterManager::hasMozMallocUsableSize. A
+ * // mandatory boolean.
+ * "hasMozMallocUsableSize": true,
+ *
+ * // The memory reports. A mandatory array.
+ * "reports": [
+ * // The properties correspond to the arguments of
+ * // nsIHandleReportCallback::callback. Every one is mandatory.
+ * {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
+ * "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
+ * {"process":"Main Process (pid 12345)", "path":"heap-allocated",
+ * "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."},
+ * {"process":"Main Process (pid 12345)", "path":"vsize",
+ * "kind":1, "units":0, "amount":10000000, "description":"Vsize."}
+ * ]
+ * }
+ */
+ void dumpMemoryReportsToNamedFile(in AString aFilename,
+ in nsIFinishDumpingCallback aFinishDumping,
+ in nsISupports aFinishDumpingData,
+ in boolean aAnonymize,
+ in boolean aMinimizeMemoryUsage);
+
+ /**
+ * Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory
+ * reports for this process and its child processes to files in the tmp
+ * directory called memory-reports-<identifier>-<pid>.json.gz (or something
+ * similar, such as memory-reports-<identifier>-<pid>-1.json.gz; no existing
+ * file will be overwritten).
+ *
+ * If DMD is enabled, this method also dumps gzipped DMD output for this
+ * process and its child processes to files in the tmp directory called
+ * dmd-<identifier>-<pid>.txt.gz (or something similar; again, no existing
+ * file will be overwritten).
+ *
+ * @param aIdentifier this identifier will appear in the filename of our
+ * about:memory dump and those of our children.
+ *
+ * If the identifier is empty, the implementation may set it arbitrarily
+ * and use that new value for its own dump and the dumps of its child
+ * processes. For example, the implementation may set |aIdentifier| to the
+ * number of seconds since the epoch.
+ *
+ * @param aAnonymize Should the reports be anonymized?
+ *
+ * @param aMinimizeMemoryUsage indicates whether we should run a series of
+ * GC/CC's in an attempt to reduce our memory usage before collecting our
+ * memory report.
+ */
+ void dumpMemoryInfoToTempDir(
+ in AString aIdentifier,
+ in boolean aAnonymize,
+ in boolean aMinimizeMemoryUsage);
+
+ /**
+ * Dump GC and CC logs to files in the OS's temp directory (or in
+ * $MOZ_CC_LOG_DIRECTORY, if that environment variable is specified).
+ *
+ * @param aIdentifier If aIdentifier is non-empty, this string will appear in
+ * the filenames of the logs we create (both for this process and, if
+ * aDumpChildProcesses is true, for our child processes).
+ *
+ * If aIdentifier is empty, the implementation may set it to an
+ * arbitrary value; for example, it may set aIdentifier to the number
+ * of seconds since the epoch.
+ *
+ * @param aDumpAllTraces indicates whether we should run an all-traces CC
+ * log. An all-traces log visits all objects currently eligible for cycle
+ * collection, while a non-all-traces log avoids visiting some objects
+ * which we know are reachable.
+ *
+ * All-traces logs are much bigger than the alternative, but they may be
+ * helpful when trying to understand why a particular object is alive. For
+ * example, a non-traces-log will skip references held by an active
+ * document; if your object is being held alive by such a document, you
+ * probably want to see those references.
+ *
+ * @param aDumpChildProcesses indicates whether we should call
+ * DumpGCAndCCLogsToFile in our child processes. If so, the child processes
+ * will dump their children, and so on.
+ *
+ */
+ void dumpGCAndCCLogsToFile(in AString aIdentifier,
+ in bool aDumpAllTraces,
+ in bool aDumpChildProcesses,
+ in nsIDumpGCAndCCLogsCallback aCallback);
+
+ /**
+ * Like |dumpGCAndCCLogsToFile|, but sends the logs to the given log
+ * sink object instead of accessing the filesystem directly, and
+ * dumps the current process only.
+ */
+ void dumpGCAndCCLogsToSink(in bool aDumpAllTraces,
+ in nsICycleCollectorLogSink aSink);
+};
diff --git a/xpcom/base/nsIMemoryReporter.idl b/xpcom/base/nsIMemoryReporter.idl
new file mode 100644
index 0000000000..05b5701519
--- /dev/null
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -0,0 +1,615 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+%{C++
+#include <stdio.h>
+%}
+
+interface mozIDOMWindowProxy;
+interface nsIRunnable;
+interface nsISimpleEnumerator;
+[ptr] native FILE(FILE);
+
+/*
+ * Memory reporters measure Firefox's memory usage. They are primarily used to
+ * generate the about:memory page. You should read
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Memory_reporting
+ * before writing a memory reporter.
+ */
+
+[scriptable, function, uuid(62ef0e1c-dbd6-11e3-aa75-3c970e9f4238)]
+interface nsIHandleReportCallback : nsISupports
+{
+ /*
+ * The arguments to the callback are as follows.
+ *
+ *
+ * |process| The name of the process containing this reporter. Each
+ * reporter initially has "" in this field, indicating that it applies to the
+ * current process. (This is true even for reporters in a child process.)
+ * When a reporter from a child process is copied into the main process, the
+ * copy has its 'process' field set appropriately.
+ *
+ *
+ * |path| The path that this memory usage should be reported under. Paths
+ * are '/'-delimited, eg. "a/b/c".
+ *
+ * Each reporter can be viewed as representing a leaf node in a tree.
+ * Internal nodes of the tree don't have reporters. So, for example, the
+ * reporters "explicit/a/b", "explicit/a/c", "explicit/d/e", and
+ * "explicit/d/f" define this tree:
+ *
+ * explicit
+ * |--a
+ * | |--b [*]
+ * | \--c [*]
+ * \--d
+ * |--e [*]
+ * \--f [*]
+ *
+ * Nodes marked with a [*] have a reporter. Notice that the internal
+ * nodes are implicitly defined by the paths.
+ *
+ * Nodes within a tree should not overlap measurements, otherwise the
+ * parent node measurements will be double-counted. So in the example
+ * above, |b| should not count any allocations counted by |c|, and vice
+ * versa.
+ *
+ * All nodes within each tree must have the same units.
+ *
+ * If you want to include a '/' not as a path separator, e.g. because the
+ * path contains a URL, you need to convert each '/' in the URL to a '\'.
+ * Consumers of the path will undo this change. Any other '\' character
+ * in a path will also be changed. This is clumsy but hasn't caused any
+ * problems so far.
+ *
+ * The paths of all reporters form a set of trees. Trees can be
+ * "degenerate", i.e. contain a single entry with no '/'.
+ *
+ *
+ * |kind| There are three kinds of memory reporters.
+ *
+ * - HEAP: reporters measuring memory allocated by the heap allocator,
+ * e.g. by calling malloc, calloc, realloc, memalign, operator new, or
+ * operator new[]. Reporters in this category must have units
+ * UNITS_BYTES.
+ *
+ * - NONHEAP: reporters measuring memory which the program explicitly
+ * allocated, but does not live on the heap. Such memory is commonly
+ * allocated by calling one of the OS's memory-mapping functions (e.g.
+ * mmap, VirtualAlloc, or vm_allocate). Reporters in this category
+ * must have units UNITS_BYTES.
+ *
+ * - OTHER: reporters which don't fit into either of these categories.
+ * They can have any units.
+ *
+ * The kind only matters for reporters in the "explicit" tree;
+ * aboutMemory.js uses it to calculate "heap-unclassified".
+ *
+ *
+ * |units| The units on the reporter's amount. One of the following.
+ *
+ * - BYTES: The amount contains a number of bytes.
+ *
+ * - COUNT: The amount is an instantaneous count of things currently in
+ * existence. For instance, the number of tabs currently open would have
+ * units COUNT.
+ *
+ * - COUNT_CUMULATIVE: The amount contains the number of times some event
+ * has occurred since the application started up. For instance, the
+ * number of times the user has opened a new tab would have units
+ * COUNT_CUMULATIVE.
+ *
+ * The amount returned by a reporter with units COUNT_CUMULATIVE must
+ * never decrease over the lifetime of the application.
+ *
+ * - PERCENTAGE: The amount contains a fraction that should be expressed as
+ * a percentage. NOTE! The |amount| field should be given a value 100x
+ * the actual percentage; this number will be divided by 100 when shown.
+ * This allows a fractional percentage to be shown even though |amount| is
+ * an integer. E.g. if the actual percentage is 12.34%, |amount| should
+ * be 1234.
+ *
+ * Values greater than 100% are allowed.
+ *
+ *
+ * |amount| The numeric value reported by this memory reporter. Accesses
+ * can fail if something goes wrong when getting the amount.
+ *
+ *
+ * |description| A human-readable description of this memory usage report.
+ */
+ void callback(in ACString process, in AUTF8String path, in int32_t kind,
+ in int32_t units, in int64_t amount,
+ in AUTF8String description, in nsISupports data);
+};
+
+/*
+ * An nsIMemoryReporter reports one or more memory measurements via a
+ * callback function which is called once for each measurement.
+ *
+ * An nsIMemoryReporter that reports a single measurement is sometimes called a
+ * "uni-reporter". One that reports multiple measurements is sometimes called
+ * a "multi-reporter".
+ *
+ * aboutMemory.js is the most important consumer of memory reports. It
+ * places the following constraints on reports.
+ *
+ * - All reports within a single sub-tree must have the same units.
+ *
+ * - There may be an "explicit" tree. If present, it represents
+ * non-overlapping regions of memory that have been explicitly allocated with
+ * an OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a
+ * heap-level allocation (e.g. malloc/calloc/operator new). Reporters in
+ * this tree must have kind HEAP or NONHEAP, units BYTES.
+ *
+ * It is preferred, but not required, that report descriptions use complete
+ * sentences (i.e. start with a capital letter and end with a period, or
+ * similar).
+ */
+[scriptable, uuid(92a36db1-46bd-4fe6-988e-47db47236d8b)]
+interface nsIMemoryReporter : nsISupports
+{
+ /*
+ * Run the reporter.
+ *
+ * If |anonymize| is true, the memory reporter should anonymize any
+ * privacy-sensitive details in memory report paths, by replacing them with a
+ * string such as "<anonymized>". Anonymized memory reports may be sent
+ * automatically via crash reports or telemetry.
+ *
+ * The following things are considered privacy-sensitive.
+ *
+ * - Content domains and URLs, and information derived from them.
+ * - Content data, such as strings.
+ * - Details about content code, such as filenames, function names or stack
+ * traces.
+ * - Details about or data from the user's system, such as filenames.
+ * - Running apps.
+ *
+ * In short, anything that could identify parts of the user's browsing
+ * history is considered privacy-sensitive.
+ *
+ * The following thing are not considered privacy-sensitive.
+ *
+ * - Chrome domains and URLs.
+ * - Information about installed extensions.
+ */
+ void collectReports(in nsIHandleReportCallback callback,
+ in nsISupports data,
+ in boolean anonymize);
+
+ /*
+ * Kinds. See the |kind| comment in nsIHandleReportCallback.
+ */
+ const int32_t KIND_NONHEAP = 0;
+ const int32_t KIND_HEAP = 1;
+ const int32_t KIND_OTHER = 2;
+
+ /*
+ * Units. See the |units| comment in nsIHandleReportCallback.
+ */
+ const int32_t UNITS_BYTES = 0;
+ const int32_t UNITS_COUNT = 1;
+ const int32_t UNITS_COUNT_CUMULATIVE = 2;
+ const int32_t UNITS_PERCENTAGE = 3;
+};
+
+[scriptable, function, uuid(548b3909-c04d-4ca6-8466-b8bee3837457)]
+interface nsIFinishReportingCallback : nsISupports
+{
+ void callback(in nsISupports data);
+};
+
+[scriptable, function, uuid(1a80cd0f-0d9e-4397-be69-68ad28fe5175)]
+interface nsIHeapAllocatedCallback : nsISupports
+{
+ void callback(in int64_t bytesAllocated);
+};
+
+[scriptable, builtinclass, uuid(2998574d-8993-407a-b1a5-8ad7417653e1)]
+interface nsIMemoryReporterManager : nsISupports
+{
+ /*
+ * Initialize.
+ */
+ [must_use] void init();
+
+ /*
+ * Register the given nsIMemoryReporter. The Manager service will hold a
+ * strong reference to the given reporter, and will be responsible for freeing
+ * the reporter at shutdown. You may manually unregister the reporter with
+ * unregisterStrongReporter() at any point.
+ */
+ void registerStrongReporter(in nsIMemoryReporter reporter);
+ void registerStrongAsyncReporter(in nsIMemoryReporter reporter);
+
+ /*
+ * Like registerReporter, but the Manager service will hold a weak reference
+ * via a raw pointer to the given reporter. The reporter should be
+ * unregistered before shutdown.
+ * You cannot register JavaScript components with this function! Always
+ * register your JavaScript components with registerStrongReporter().
+ */
+ void registerWeakReporter(in nsIMemoryReporter reporter);
+ void registerWeakAsyncReporter(in nsIMemoryReporter reporter);
+
+ /*
+ * Unregister the given memory reporter, which must have been registered with
+ * registerStrongReporter(). You normally don't need to unregister your
+ * strong reporters, as nsIMemoryReporterManager will take care of that at
+ * shutdown.
+ */
+ void unregisterStrongReporter(in nsIMemoryReporter reporter);
+
+ /*
+ * Unregister the given memory reporter, which must have been registered with
+ * registerWeakReporter().
+ */
+ void unregisterWeakReporter(in nsIMemoryReporter reporter);
+
+ /*
+ * These functions should only be used for testing purposes.
+ */
+ void blockRegistrationAndHideExistingReporters();
+ void unblockRegistrationAndRestoreOriginalReporters();
+ void registerStrongReporterEvenIfBlocked(in nsIMemoryReporter aReporter);
+
+ /*
+ * Get memory reports for the current process and all child processes.
+ * |handleReport| is called for each report, and |finishReporting| is called
+ * once all reports have been handled.
+ *
+ * |finishReporting| is called even if, for example, some child processes
+ * fail to report back. However, calls to this method will silently and
+ * immediately abort -- and |finishReporting| will not be called -- if a
+ * previous getReports() call is still in flight, i.e. if it has not yet
+ * finished invoking |finishReporting|. The silent abort is because the
+ * in-flight request will finish soon, and the caller would very likely just
+ * catch and ignore any error anyway.
+ *
+ * If |anonymize| is true, it indicates that the memory reporters should
+ * anonymize any privacy-sensitive data (see above).
+ */
+ void getReports(in nsIHandleReportCallback handleReport,
+ in nsISupports handleReportData,
+ in nsIFinishReportingCallback finishReporting,
+ in nsISupports finishReportingData,
+ in boolean anonymize);
+
+ /*
+ * As above, but: If |minimizeMemoryUsage| is true, then each process will
+ * minimize its memory usage (see the |minimizeMemoryUsage| method) before
+ * gathering its report. If DMD is enabled and |DMDDumpIdent| is non-empty
+ * then write a DMD report to a file in the usual temporary directory (see
+ * |dumpMemoryInfoToTempDir| in |nsIMemoryInfoDumper|.)
+ */
+ [noscript] void
+ getReportsExtended(in nsIHandleReportCallback handleReport,
+ in nsISupports handleReportData,
+ in nsIFinishReportingCallback finishReporting,
+ in nsISupports finishReportingData,
+ in boolean anonymize,
+ in boolean minimizeMemoryUsage,
+ in AString DMDDumpIdent);
+
+ /*
+ * As above, but if DMD is enabled and |DMDFile| is non-null then
+ * write a DMD report to that file and close it.
+ */
+ [noscript] void
+ getReportsForThisProcessExtended(in nsIHandleReportCallback handleReport,
+ in nsISupports handleReportData,
+ in boolean anonymize,
+ in FILE DMDFile,
+ in nsIFinishReportingCallback finishReporting,
+ in nsISupports finishReportingData);
+
+ /*
+ * Called by an asynchronous memory reporter upon completion.
+ */
+ [noscript] void endReport();
+
+ /*
+ * The memory reporter manager, for the most part, treats reporters
+ * registered with it as a black box. However, there are some
+ * "distinguished" amounts (as could be reported by a memory reporter) that
+ * the manager provides as attributes, because they are sufficiently
+ * interesting that we want external code (e.g. telemetry) to be able to rely
+ * on them.
+ *
+ * Note that these are not reporters and so getReports() does not look at
+ * them. However, distinguished amounts can be embedded in a reporter.
+ *
+ * Access to these attributes can fail. In particular, some of them are not
+ * available on all platforms.
+ *
+ * If you add a new distinguished amount, please update
+ * toolkit/components/aboutmemory/tests/test_memoryReporters.xul.
+ *
+ * |vsize| (UNITS_BYTES) The virtual size, i.e. the amount of address space
+ * taken up.
+ *
+ * |vsizeMaxContiguous| (UNITS_BYTES) The size of the largest contiguous
+ * block of virtual memory.
+ *
+ * |resident| (UNITS_BYTES) The resident size (a.k.a. RSS or physical memory
+ * used).
+ *
+ * |residentFast| (UNITS_BYTES) This is like |resident|, but on Mac OS
+ * |resident| can purge pages, which is slow. It also affects the result of
+ * |residentFast|, and so |resident| and |residentFast| should not be used
+ * together.
+ *
+ * |residentPeak| (UNITS_BYTES) The peak resident size.
+ *
+ * |residentUnique| (UNITS_BYTES) The unique set size (a.k.a. USS).
+ *
+ * |heapAllocated| (UNITS_BYTES) Memory mapped by the heap allocator.
+ *
+ * |heapOverheadFraction| (UNITS_PERCENTAGE) In the heap allocator, this is
+ * the fraction of committed heap bytes that are overhead. Like all
+ * UNITS_PERCENTAGE measurements, its amount is multiplied by 100x so it can
+ * be represented by an int64_t.
+ *
+ * |JSMainRuntimeGCHeap| (UNITS_BYTES) Size of the main JS runtime's GC
+ * heap.
+ *
+ * |JSMainRuntimeTemporaryPeak| (UNITS_BYTES) Peak size of the transient
+ * storage in the main JSRuntime.
+ *
+ * |JSMainRuntimeCompartments{System,User}| (UNITS_COUNT) The number of
+ * {system,user} compartments in the main JS runtime.
+ *
+ * |JSMainRuntimeRealms{System,User}| (UNITS_COUNT) The number of
+ * {system,user} realms in the main JS runtime.
+ *
+ * |imagesContentUsedUncompressed| (UNITS_BYTES) Memory used for decoded
+ * raster images in content.
+ *
+ * |storageSQLite| (UNITS_BYTES) Memory used by SQLite.
+ *
+ * |lowMemoryEventsPhysical| (UNITS_COUNT_CUMULATIVE)
+ * The number of low-physical-memory events that have occurred since the
+ * process started.
+ *
+ * |ghostWindows| (UNITS_COUNT) A cached value of the number of ghost
+ * windows. This should have been updated within the past 60s.
+ *
+ * |pageFaultsHard| (UNITS_COUNT_CUMULATIVE) The number of hard (a.k.a.
+ * major) page faults that have occurred since the process started.
+ */
+ [must_use] readonly attribute int64_t vsize;
+ [must_use] readonly attribute int64_t vsizeMaxContiguous;
+ [must_use] readonly attribute int64_t resident;
+ [must_use] readonly attribute int64_t residentFast;
+ [must_use] readonly attribute int64_t residentPeak;
+ [must_use] readonly attribute int64_t residentUnique;
+
+ [must_use] readonly attribute int64_t heapAllocated;
+ [must_use] readonly attribute int64_t heapOverheadFraction;
+
+ [must_use] readonly attribute int64_t JSMainRuntimeGCHeap;
+ [must_use] readonly attribute int64_t JSMainRuntimeTemporaryPeak;
+ [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsSystem;
+ [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsUser;
+ [must_use] readonly attribute int64_t JSMainRuntimeRealmsSystem;
+ [must_use] readonly attribute int64_t JSMainRuntimeRealmsUser;
+
+ [must_use] readonly attribute int64_t imagesContentUsedUncompressed;
+
+ [must_use] readonly attribute int64_t storageSQLite;
+
+ [must_use] readonly attribute int64_t lowMemoryEventsPhysical;
+
+ [must_use] readonly attribute int64_t ghostWindows;
+
+ [must_use] readonly attribute int64_t pageFaultsHard;
+
+ /*
+ * This attribute indicates if moz_malloc_usable_size() works.
+ */
+ [infallible] readonly attribute boolean hasMozMallocUsableSize;
+
+ /*
+ * These attributes indicate DMD's status. "Enabled" means enabled at
+ * build-time.
+ */
+ [infallible] readonly attribute boolean isDMDEnabled;
+ [infallible] readonly attribute boolean isDMDRunning;
+
+ /*
+ * Run a series of GC/CC's in an attempt to minimize the application's memory
+ * usage. When we're finished doing this for the current process, we invoke
+ * the given runnable if it's not null. We do not wait for any child processes
+ * that might be doing their own minimization via child-mmu-request to finish.
+ */
+ [must_use] void minimizeMemoryUsage(in nsIRunnable callback);
+
+ /*
+ * Measure the memory that is known to be owned by this tab, split up into
+ * several broad categories. Note that this will be an underestimate of the
+ * true number, due to imperfect memory reporter coverage (corresponding to
+ * about:memory's "heap-unclassified"), and due to some memory shared between
+ * tabs not being counted.
+ *
+ * The time taken for the measurement (split into JS and non-JS parts) is
+ * also returned.
+ */
+ [must_use]
+ void sizeOfTab(in mozIDOMWindowProxy window,
+ out int64_t jsObjectsSize, out int64_t jsStringsSize,
+ out int64_t jsOtherSize, out int64_t domSize,
+ out int64_t styleSize, out int64_t otherSize,
+ out int64_t totalSize,
+ out double jsMilliseconds, out double nonJSMilliseconds);
+};
+
+%{C++
+
+#include "js/TypeDecls.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+// All the following registration/unregistration functions don't use
+// [[nodiscard]] because ignoring failures is common and reasonable.
+
+// Register a memory reporter. The manager service will hold a strong
+// reference to this reporter.
+XPCOM_API(nsresult) RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter);
+XPCOM_API(nsresult) RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter);
+
+// Register a memory reporter. The manager service will hold a weak reference
+// to this reporter.
+XPCOM_API(nsresult) RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter);
+XPCOM_API(nsresult) RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter);
+
+// Unregister a strong memory reporter.
+XPCOM_API(nsresult) UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter);
+
+// Unregister a weak memory reporter.
+XPCOM_API(nsresult) UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter);
+
+// The memory reporter manager provides access to several distinguished
+// amounts via attributes. Some of these amounts are provided by Gecko
+// components that cannot be accessed directly from XPCOM code. So we provide
+// the following functions for those components to be registered with the
+// manager.
+
+typedef int64_t (*InfallibleAmountFn)();
+
+#define DECL_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \
+ nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn);
+#define DECL_UNREGISTER_DISTINGUISHED_AMOUNT(name) \
+ nsresult Unregister##name##DistinguishedAmount();
+
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap)
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak)
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem)
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsSystem)
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser)
+
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed)
+DECL_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed)
+
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite)
+DECL_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite)
+
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
+
+DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)
+
+#undef DECL_REGISTER_DISTINGUISHED_AMOUNT
+#undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT
+
+// Likewise for per-tab measurement.
+
+typedef nsresult (*JSSizeOfTabFn)(JSObject* aObj,
+ size_t* aJsObjectsSize,
+ size_t* aJsStringSize,
+ size_t* aJsPrivateSize,
+ size_t* aJsOtherSize);
+typedef nsresult (*NonJSSizeOfTabFn)(nsPIDOMWindowOuter* aWindow,
+ size_t* aDomSize,
+ size_t* aStyleSize,
+ size_t* aOtherSize);
+
+nsresult RegisterJSSizeOfTab(JSSizeOfTabFn aSizeOfTabFn);
+nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn);
+
+}
+
+#if defined(MOZ_DMD)
+#if !defined(MOZ_MEMORY)
+#error "MOZ_DMD requires MOZ_MEMORY"
+#endif
+
+#include "DMD.h"
+
+#define MOZ_REPORT(ptr) mozilla::dmd::Report(ptr)
+#define MOZ_REPORT_ON_ALLOC(ptr) mozilla::dmd::ReportOnAlloc(ptr)
+
+#else
+
+#define MOZ_REPORT(ptr)
+#define MOZ_REPORT_ON_ALLOC(ptr)
+
+#endif // defined(MOZ_DMD)
+
+// Functions generated via this macro should be used by all traversal-based
+// memory reporters. Such functions return |moz_malloc_size_of(ptr)|; this
+// will always be zero on some obscure platforms.
+//
+// You might be wondering why we have a macro that creates multiple functions
+// that differ only in their name, instead of a single MallocSizeOf function.
+// It's mostly to help with DMD integration, though it sometimes also helps
+// with debugging and temporary ad hoc profiling. The function name chosen
+// doesn't matter greatly, but it's best to make it similar to the path used by
+// the relevant memory reporter(s).
+#define MOZ_DEFINE_MALLOC_SIZE_OF(fn) \
+ static size_t fn(const void* aPtr) \
+ { \
+ MOZ_REPORT(aPtr); \
+ return moz_malloc_size_of(aPtr); \
+ }
+
+// This is an alternative to MOZ_DEFINE_MALLOC_SIZE_OF that defines a
+// MallocSizeOf function that can handle interior pointers.
+#ifdef MOZ_MEMORY
+
+#include "mozmemory.h"
+
+#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \
+ static size_t fn(const void* aPtr) \
+ { \
+ jemalloc_ptr_info_t info; \
+ jemalloc_ptr_info(aPtr, &info); \
+ MOZ_REPORT(info.addr); \
+ return jemalloc_ptr_is_live(&info) ? info.size : 0; \
+ }
+
+#else
+
+#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \
+ static size_t fn(const void* aPtr) \
+ { \
+ return 0; \
+ }
+
+#endif
+
+// Functions generated by the next two macros should be used by wrapping
+// allocators that report heap blocks as soon as they are allocated and
+// unreport them as soon as they are freed. Such allocators are used in cases
+// where we have third-party code that we cannot modify. The two functions
+// must always be used in tandem.
+#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(fn) \
+ static size_t fn(const void* aPtr) \
+ { \
+ MOZ_REPORT_ON_ALLOC(aPtr); \
+ return moz_malloc_size_of(aPtr); \
+ }
+#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(fn) \
+ static size_t fn(const void* aPtr) \
+ { \
+ return moz_malloc_size_of(aPtr); \
+ }
+
+// This macro assumes the presence of appropriate |aHandleReport| and |aData|
+// variables. The (void) is there because we should always ignore the return
+// value of the callback, because callback failures aren't fatal.
+#define MOZ_COLLECT_REPORT(path, kind, units, amount, description) \
+ (void)aHandleReport->Callback(""_ns, nsLiteralCString(path), \
+ kind, units, amount, \
+ nsLiteralCString(description), aData)
+
+%}
diff --git a/xpcom/base/nsINIParser.cpp b/xpcom/base/nsINIParser.cpp
new file mode 100644
index 0000000000..1447aa6fbf
--- /dev/null
+++ b/xpcom/base/nsINIParser.cpp
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Moz headers (alphabetical)
+#include "nsCRTGlue.h"
+#include "nsError.h"
+#include "nsIFile.h"
+#include "nsINIParser.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Try.h"
+#include "mozilla/URLPreloader.h"
+
+using namespace mozilla;
+
+nsresult nsINIParser::Init(nsIFile* aFile) {
+ nsCString result;
+ MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile));
+
+ return InitFromString(result);
+}
+
+static const char kNL[] = "\r\n";
+static const char kEquals[] = "=";
+static const char kWhitespace[] = " \t";
+static const char kRBracket[] = "]";
+
+nsresult nsINIParser::InitFromString(const nsCString& aStr) {
+ nsCString fileContents;
+ char* buffer;
+
+ if (StringHead(aStr, 3) == "\xEF\xBB\xBF") {
+ // Someone set us up the Utf-8 BOM
+ // This case is easy, since we assume that BOM-less
+ // files are Utf-8 anyway. Just skip the BOM and process as usual.
+ fileContents.Append(aStr);
+ buffer = fileContents.BeginWriting() + 3;
+ } else {
+ if (StringHead(aStr, 2) == "\xFF\xFE") {
+ // Someone set us up the Utf-16LE BOM
+ nsDependentSubstring str(reinterpret_cast<const char16_t*>(aStr.get()),
+ aStr.Length() / 2);
+
+ AppendUTF16toUTF8(Substring(str, 1), fileContents);
+ } else {
+ fileContents.Append(aStr);
+ }
+
+ buffer = fileContents.BeginWriting();
+ }
+
+ char* currSection = nullptr;
+
+ // outer loop tokenizes into lines
+ while (char* token = NS_strtok(kNL, &buffer)) {
+ if (token[0] == '#' || token[0] == ';') { // it's a comment
+ continue;
+ }
+
+ token = (char*)NS_strspnp(kWhitespace, token);
+ if (!*token) { // empty line
+ continue;
+ }
+
+ if (token[0] == '[') { // section header!
+ ++token;
+ currSection = token;
+
+ char* rb = NS_strtok(kRBracket, &token);
+ if (!rb || NS_strtok(kWhitespace, &token)) {
+ // there's either an unclosed [Section or a [Section]Moretext!
+ // we could frankly decide that this INI file is malformed right
+ // here and stop, but we won't... keep going, looking for
+ // a well-formed [section] to continue working with
+ currSection = nullptr;
+ }
+
+ continue;
+ }
+
+ if (!currSection) {
+ // If we haven't found a section header (or we found a malformed
+ // section header), don't bother parsing this line.
+ continue;
+ }
+
+ char* key = token;
+ char* e = NS_strtok(kEquals, &token);
+ if (!e || !token) {
+ continue;
+ }
+
+ SetString(currSection, key, token);
+ }
+
+ return NS_OK;
+}
+
+bool nsINIParser::IsValidSection(const char* aSection) {
+ if (aSection[0] == '\0') {
+ return false;
+ }
+
+ const char* found = strpbrk(aSection, "\r\n[]");
+ return found == nullptr;
+}
+
+bool nsINIParser::IsValidKey(const char* aKey) {
+ if (aKey[0] == '\0') {
+ return false;
+ }
+
+ const char* found = strpbrk(aKey, "\r\n=");
+ return found == nullptr;
+}
+
+bool nsINIParser::IsValidValue(const char* aValue) {
+ const char* found = strpbrk(aValue, "\r\n");
+ return found == nullptr;
+}
+
+nsresult nsINIParser::GetString(const char* aSection, const char* aKey,
+ nsACString& aResult) {
+ if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ INIValue* val;
+ mSections.Get(aSection, &val);
+
+ while (val) {
+ if (strcmp(val->key, aKey) == 0) {
+ aResult.Assign(val->value);
+ return NS_OK;
+ }
+
+ val = val->next.get();
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsINIParser::GetString(const char* aSection, const char* aKey,
+ char* aResult, uint32_t aResultLen) {
+ if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ INIValue* val;
+ mSections.Get(aSection, &val);
+
+ while (val) {
+ if (strcmp(val->key, aKey) == 0) {
+ strncpy(aResult, val->value, aResultLen);
+ aResult[aResultLen - 1] = '\0';
+ if (strlen(val->value) >= aResultLen) {
+ return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+
+ return NS_OK;
+ }
+
+ val = val->next.get();
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsINIParser::GetSections(INISectionCallback aCB, void* aClosure) {
+ for (const auto& key : mSections.Keys()) {
+ if (!aCB(key, aClosure)) {
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsINIParser::GetStrings(const char* aSection, INIStringCallback aCB,
+ void* aClosure) {
+ if (!IsValidSection(aSection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ INIValue* val;
+
+ for (mSections.Get(aSection, &val); val; val = val->next.get()) {
+ if (!aCB(val->key, val->value, aClosure)) {
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsINIParser::SetString(const char* aSection, const char* aKey,
+ const char* aValue) {
+ if (!IsValidSection(aSection) || !IsValidKey(aKey) || !IsValidValue(aValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mSections.WithEntryHandle(aSection, [&](auto&& entry) {
+ if (!entry) {
+ entry.Insert(MakeUnique<INIValue>(aKey, aValue));
+ return;
+ }
+
+ INIValue* v = entry->get();
+
+ // Check whether this key has already been specified; overwrite
+ // if so, or append if not.
+ while (v) {
+ if (!strcmp(aKey, v->key)) {
+ v->SetValue(aValue);
+ break;
+ }
+ if (!v->next) {
+ v->next = MakeUnique<INIValue>(aKey, aValue);
+ break;
+ }
+ v = v->next.get();
+ }
+ NS_ASSERTION(v, "v should never be null coming out of this loop");
+ });
+
+ return NS_OK;
+}
+
+nsresult nsINIParser::DeleteString(const char* aSection, const char* aKey) {
+ if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ INIValue* val;
+ if (!mSections.Get(aSection, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Special case the first result
+ if (strcmp(val->key, aKey) == 0) {
+ if (!val->next) {
+ mSections.Remove(aSection);
+ } else {
+ mSections.InsertOrUpdate(aSection, std::move(val->next));
+ delete val;
+ }
+ return NS_OK;
+ }
+
+ while (val->next) {
+ if (strcmp(val->next->key, aKey) == 0) {
+ val->next = std::move(val->next->next);
+
+ return NS_OK;
+ }
+
+ val = val->next.get();
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsINIParser::DeleteSection(const char* aSection) {
+ if (!IsValidSection(aSection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mSections.Remove(aSection)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsINIParser::RenameSection(const char* aSection,
+ const char* aNewName) {
+ if (!IsValidSection(aSection) || !IsValidSection(aNewName)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mSections.Contains(aNewName)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mozilla::UniquePtr<INIValue> val;
+ if (mSections.Remove(aSection, &val)) {
+ mSections.InsertOrUpdate(aNewName, std::move(val));
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsINIParser::WriteToFile(nsIFile* aFile) {
+ nsCString buffer;
+
+ WriteToString(buffer);
+
+ FILE* writeFile;
+ nsresult rv = aFile->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ unsigned int length = buffer.Length();
+
+ if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+ fclose(writeFile);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ fclose(writeFile);
+ return NS_OK;
+}
+
+void nsINIParser::WriteToString(nsACString& aOutput) {
+ for (const auto& entry : mSections) {
+ aOutput.AppendPrintf("[%s]\n", entry.GetKey());
+ INIValue* val = entry.GetWeak();
+ while (val) {
+ aOutput.AppendPrintf("%s=%s\n", val->key, val->value);
+ val = val->next.get();
+ }
+ aOutput.AppendLiteral("\n");
+ }
+}
diff --git a/xpcom/base/nsINIParser.h b/xpcom/base/nsINIParser.h
new file mode 100644
index 0000000000..3b26a0d511
--- /dev/null
+++ b/xpcom/base/nsINIParser.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file was shamelessly copied from mozilla/xpinstall/wizard/unix/src2
+
+#ifndef nsINIParser_h__
+#define nsINIParser_h__
+
+#ifdef MOZILLA_INTERNAL_API
+# define nsINIParser nsINIParser_internal
+#endif
+
+#include "nscore.h"
+#include "nsClassHashtable.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdio.h>
+
+class nsIFile;
+
+class nsINIParser {
+ public:
+ nsINIParser() {}
+ ~nsINIParser() = default;
+
+ /**
+ * Initialize the INIParser with a nsIFile. If this method fails, no
+ * other methods should be called. This method reads and parses the file,
+ * the class does not hold a file handle open. An instance must only be
+ * initialized once.
+ */
+ nsresult Init(nsIFile* aFile);
+
+ nsresult InitFromString(const nsCString& aStr);
+
+ /**
+ * Callback for GetSections
+ * @return false to stop enumeration, or true to continue.
+ */
+ typedef bool (*INISectionCallback)(const char* aSection, void* aClosure);
+
+ /**
+ * Enumerate the sections within the INI file.
+ */
+ nsresult GetSections(INISectionCallback aCB, void* aClosure);
+
+ /**
+ * Callback for GetStrings
+ * @return false to stop enumeration, or true to continue
+ */
+ typedef bool (*INIStringCallback)(const char* aString, const char* aValue,
+ void* aClosure);
+
+ /**
+ * Enumerate the strings within a section. If the section does
+ * not exist, this function will silently return.
+ */
+ nsresult GetStrings(const char* aSection, INIStringCallback aCB,
+ void* aClosure);
+
+ /**
+ * Get the value of the specified key in the specified section
+ * of the INI file represented by this instance.
+ *
+ * @param aSection section name
+ * @param aKey key name
+ * @param aResult the value found
+ * @throws NS_ERROR_FAILURE if the specified section/key could not be
+ * found.
+ */
+ nsresult GetString(const char* aSection, const char* aKey,
+ nsACString& aResult);
+
+ /**
+ * Alternate signature of GetString that uses a pre-allocated buffer
+ * instead of a nsACString (for use in the standalone glue before
+ * the glue is initialized).
+ *
+ * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if the aResult buffer is not
+ * large enough for the data. aResult will be filled with as
+ * much data as possible.
+ *
+ * @see GetString [1]
+ */
+ nsresult GetString(const char* aSection, const char* aKey, char* aResult,
+ uint32_t aResultLen);
+
+ /**
+ * Sets the value of the specified key in the specified section. The section
+ * is created if it does not already exist.
+ *
+ * @oaram aSection section name
+ * @param aKey key name
+ * @param aValue the value to set
+ */
+ nsresult SetString(const char* aSection, const char* aKey,
+ const char* aValue);
+
+ /**
+ * Deletes the value of the specified key in the specified section.
+ *
+ * @param aSection section name
+ * @param aKey key name
+ *
+ * @throws NS_ERROR_FAILURE if the string was not set.
+ */
+ nsresult DeleteString(const char* aSection, const char* aKey);
+
+ /**
+ * Deletes the specified section.
+ *
+ * @param aSection section name
+ *
+ * @throws NS_ERROR_FAILURE if the section did not exist.
+ */
+ nsresult DeleteSection(const char* aSection);
+
+ /**
+ * Renames the specified section.
+ *
+ * @param aSection section name
+ * @param aNewName new section name
+ *
+ * @throws NS_ERROR_FAILURE if the section did not exist.
+ * @throws NS_ERROR_ILLEGAL_VALUE if the new section name already exists.
+ */
+ nsresult RenameSection(const char* aSection, const char* aNewName);
+
+ /**
+ * Writes the ini data to disk.
+ * @param aFile the file to write to
+ * @throws NS_ERROR_FAILURE on failure.
+ */
+ nsresult WriteToFile(nsIFile* aFile);
+
+ void WriteToString(nsACString& aOutput);
+
+ private:
+ struct INIValue {
+ INIValue(const char* aKey, const char* aValue)
+ : key(strdup(aKey)), value(strdup(aValue)) {}
+
+ ~INIValue() {
+ delete key;
+ delete value;
+ }
+
+ void SetValue(const char* aValue) {
+ delete value;
+ value = strdup(aValue);
+ }
+
+ const char* key;
+ const char* value;
+ mozilla::UniquePtr<INIValue> next;
+ };
+
+ nsClassHashtable<nsCharPtrHashKey, INIValue> mSections;
+
+ bool IsValidSection(const char* aSection);
+ bool IsValidKey(const char* aKey);
+ bool IsValidValue(const char* aValue);
+};
+
+#endif /* nsINIParser_h__ */
diff --git a/xpcom/base/nsISecurityConsoleMessage.idl b/xpcom/base/nsISecurityConsoleMessage.idl
new file mode 100644
index 0000000000..917968d83b
--- /dev/null
+++ b/xpcom/base/nsISecurityConsoleMessage.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+ * Holds localization message tag and message category
+ * for security related console messages.
+ */
+[uuid(FE9FC9B6-DDE2-11E2-A8F1-0A326188709B)]
+interface nsISecurityConsoleMessage : nsISupports
+{
+ attribute AString tag;
+ attribute AString category;
+};
+
+%{ C++
+#define NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID "@mozilla.org/securityconsole/message;1"
+%}
diff --git a/xpcom/base/nsISizeOf.h b/xpcom/base/nsISizeOf.h
new file mode 100644
index 0000000000..7d0ef4d74f
--- /dev/null
+++ b/xpcom/base/nsISizeOf.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsISizeOf_h___
+#define nsISizeOf_h___
+
+#include "mozilla/MemoryReporting.h"
+#include "nsISupports.h"
+
+#define NS_ISIZEOF_IID \
+ { \
+ 0x61d05579, 0xd7ec, 0x485c, { \
+ 0xa4, 0x0c, 0x31, 0xc7, 0x9a, 0x5c, 0xf9, 0xf3 \
+ } \
+ }
+
+class nsISizeOf : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISIZEOF_IID)
+
+ /**
+ * Measures the size of the things pointed to by the object.
+ */
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const = 0;
+
+ /**
+ * Like SizeOfExcludingThis, but also includes the size of the object itself.
+ */
+ virtual size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsISizeOf, NS_ISIZEOF_IID)
+
+#endif /* nsISizeOf_h___ */
diff --git a/xpcom/base/nsISupports.idl b/xpcom/base/nsISupports.idl
new file mode 100644
index 0000000000..45c6c5d589
--- /dev/null
+++ b/xpcom/base/nsISupports.idl
@@ -0,0 +1,60 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The mother of all xpcom interfaces.
+ */
+
+#include "nsrootidl.idl"
+
+/**
+ * Basic component object model interface. Objects which implement
+ * this interface support runtime interface discovery (QueryInterface)
+ * and a reference counted memory model (AddRef/Release). This is
+ * modelled after the win32 IUnknown API.
+ *
+ * Historically, nsISupports needed to be binary compatible with COM's
+ * IUnknown, so the IID of nsISupports is the same as it. That is no
+ * longer a goal, and hopefully nobody depends on it. We may break
+ * this compatibility at any time.
+ */
+[scriptable, uuid(00000000-0000-0000-c000-000000000046)]
+interface nsISupports {
+
+ /**
+ * A run time mechanism for interface discovery.
+ * @param aIID [in] A requested interface IID
+ * @param aInstancePtr [out] A pointer to an interface pointer to
+ * receive the result.
+ * @return <b>NS_OK</b> if the interface is supported by the associated
+ * instance, <b>NS_NOINTERFACE</b> if it is not.
+ *
+ * aInstancePtr must not be null.
+ */
+ void QueryInterface(in nsIIDRef aIID,
+ [iid_is(aIID), retval] out nsQIResult aInstancePtr);
+
+ /**
+ * Increases the reference count for this interface.
+ * The associated instance will not be deleted unless
+ * the reference count is returned to zero.
+ *
+ * @return The resulting reference count.
+ */
+ [noscript, notxpcom] MozExternalRefCountType AddRef();
+
+ /**
+ * Decreases the reference count for this interface.
+ * Generally, if the reference count returns to zero,
+ * the associated instance is deleted.
+ *
+ * @return The resulting reference count.
+ */
+ [noscript, notxpcom] MozExternalRefCountType Release();
+};
+
+%{C++
+#include "nsISupportsUtils.h"
+%}
diff --git a/xpcom/base/nsISupportsImpl.cpp b/xpcom/base/nsISupportsImpl.cpp
new file mode 100644
index 0000000000..c282ec14db
--- /dev/null
+++ b/xpcom/base/nsISupportsImpl.cpp
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupportsImpl.h"
+
+#include "mozilla/Assertions.h"
+#ifndef XPCOM_GLUE_AVOID_NSPR
+# include "nsPrintfCString.h"
+# include "nsThreadUtils.h"
+#endif
+
+using namespace mozilla;
+
+nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID,
+ void** aInstancePtr,
+ const QITableEntry* aEntries) {
+ do {
+ if (aIID.Equals(*aEntries->iid)) {
+ nsISupports* r = reinterpret_cast<nsISupports*>(
+ reinterpret_cast<char*>(aThis) + aEntries->offset);
+ NS_ADDREF(r);
+ *aInstancePtr = r;
+ return NS_OK;
+ }
+
+ ++aEntries;
+ } while (aEntries->iid);
+
+ *aInstancePtr = nullptr;
+ return NS_ERROR_NO_INTERFACE;
+}
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+# ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+nsAutoOwningThread::nsAutoOwningThread() : mThread(PR_GetCurrentThread()) {}
+
+void nsAutoOwningThread::AssertCurrentThreadOwnsMe(const char* msg) const {
+ if (MOZ_UNLIKELY(!IsCurrentThread())) {
+ // `msg` is a string literal by construction.
+ MOZ_CRASH_UNSAFE(msg);
+ }
+}
+
+bool nsAutoOwningThread::IsCurrentThread() const {
+ return mThread == PR_GetCurrentThread();
+}
+
+nsAutoOwningEventTarget::nsAutoOwningEventTarget()
+ : mTarget(GetCurrentSerialEventTarget()) {
+ mTarget->AddRef();
+}
+
+nsAutoOwningEventTarget::~nsAutoOwningEventTarget() {
+ nsCOMPtr<nsISerialEventTarget> target = dont_AddRef(mTarget);
+}
+
+void nsAutoOwningEventTarget ::AssertCurrentThreadOwnsMe(
+ const char* msg) const {
+ if (MOZ_UNLIKELY(!IsCurrentThread())) {
+ // `msg` is a string literal by construction.
+ MOZ_CRASH_UNSAFE(msg);
+ }
+}
+
+bool nsAutoOwningEventTarget::IsCurrentThread() const {
+ return mTarget->IsOnCurrentThread();
+}
+
+# endif
+
+namespace mozilla::detail {
+class ProxyDeleteVoidRunnable final : public CancelableRunnable {
+ public:
+ ProxyDeleteVoidRunnable(const char* aName, void* aPtr,
+ DeleteVoidFunction* aDeleteFunc)
+ : CancelableRunnable(aName), mPtr(aPtr), mDeleteFunc(aDeleteFunc) {}
+
+ NS_IMETHOD Run() override {
+ if (mPtr) {
+ mDeleteFunc(mPtr);
+ mPtr = nullptr;
+ }
+ return NS_OK;
+ }
+
+ // Mimics the behaviour in `ProxyRelease`, freeing the resource when
+ // cancelled.
+ nsresult Cancel() override { return Run(); }
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override {
+ if (mName) {
+ aName.Append(mName);
+ } else {
+ aName.AssignLiteral("ProxyDeleteVoidRunnable");
+ }
+ return NS_OK;
+ }
+# endif
+
+ private:
+ ~ProxyDeleteVoidRunnable() {
+ if (mPtr) {
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_WARNING(
+ nsPrintfCString(
+ "ProxyDeleteVoidRunnable for '%s' never run, leaking!", mName)
+ .get());
+# else
+ NS_WARNING("ProxyDeleteVoidRunnable never run, leaking!");
+# endif
+ }
+ }
+
+ void* mPtr;
+ DeleteVoidFunction* mDeleteFunc;
+};
+
+void ProxyDeleteVoid(const char* aName, nsISerialEventTarget* aTarget,
+ void* aPtr, DeleteVoidFunction* aDeleteFunc) {
+ MOZ_ASSERT(aName);
+ MOZ_ASSERT(aPtr);
+ MOZ_ASSERT(aDeleteFunc);
+ if (!aTarget) {
+ NS_WARNING(nsPrintfCString("no target for '%s', leaking!", aName).get());
+ return;
+ }
+
+ if (aTarget->IsOnCurrentThread()) {
+ aDeleteFunc(aPtr);
+ return;
+ }
+ nsresult rv = aTarget->Dispatch(
+ MakeAndAddRef<ProxyDeleteVoidRunnable>(aName, aPtr, aDeleteFunc),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(nsPrintfCString("failed to post '%s', leaking!", aName).get());
+ }
+}
+} // namespace mozilla::detail
+#endif
diff --git a/xpcom/base/nsISupportsImpl.h b/xpcom/base/nsISupportsImpl.h
new file mode 100644
index 0000000000..27ff85b385
--- /dev/null
+++ b/xpcom/base/nsISupportsImpl.h
@@ -0,0 +1,1484 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// IWYU pragma: private, include "nsISupports.h"
+
+#ifndef nsISupportsImpl_h__
+#define nsISupportsImpl_h__
+
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+
+#if !defined(XPCOM_GLUE_AVOID_NSPR)
+# include "prthread.h" /* needed for cargo-culting headers */
+#endif
+
+#include "nsDebug.h"
+#include "nsXPCOM.h"
+#include <atomic>
+#include <type_traits>
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Compiler.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MacroArgs.h"
+#include "mozilla/MacroForEach.h"
+
+#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \
+ static_assert(!std::is_destructible_v<X>, \
+ "Reference-counted class " #X \
+ " should not have a public destructor. " \
+ "Make this class's destructor non-public");
+
+inline nsISupports* ToSupports(decltype(nullptr)) { return nullptr; }
+
+inline nsISupports* ToSupports(nsISupports* aSupports) { return aSupports; }
+
+////////////////////////////////////////////////////////////////////////////////
+// Macros to help detect thread-safety:
+
+#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+
+# include "prthread.h" /* needed for thread-safety checks */
+
+class nsAutoOwningThread {
+ public:
+ nsAutoOwningThread();
+
+ // We move the actual assertion checks out-of-line to minimize code bloat,
+ // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE.
+ // To make that more safe, the public interface requires a literal string
+ // and passes that to the private interface; we can then be assured that we
+ // effectively are passing a literal string to MOZ_CRASH_UNSAFE.
+ template <int N>
+ void AssertOwnership(const char (&aMsg)[N]) const {
+ AssertCurrentThreadOwnsMe(aMsg);
+ }
+
+ bool IsCurrentThread() const;
+
+ private:
+ void AssertCurrentThreadOwnsMe(const char* aMsg) const;
+
+ void* mThread;
+};
+
+class nsISerialEventTarget;
+class nsAutoOwningEventTarget {
+ public:
+ nsAutoOwningEventTarget();
+ ~nsAutoOwningEventTarget();
+
+ // We move the actual assertion checks out-of-line to minimize code bloat,
+ // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE.
+ // To make that more safe, the public interface requires a literal string
+ // and passes that to the private interface; we can then be assured that we
+ // effectively are passing a literal string to MOZ_CRASH_UNSAFE.
+ template <int N>
+ void AssertOwnership(const char (&aMsg)[N]) const {
+ AssertCurrentThreadOwnsMe(aMsg);
+ }
+
+ bool IsCurrentThread() const;
+
+ private:
+ void AssertCurrentThreadOwnsMe(const char* aMsg) const;
+
+ nsISerialEventTarget* mTarget;
+};
+
+# define NS_DECL_OWNINGTHREAD nsAutoOwningThread _mOwningThread;
+# define NS_DECL_OWNINGEVENTTARGET nsAutoOwningEventTarget _mOwningThread;
+# define NS_ASSERT_OWNINGTHREAD(_class) \
+ _mOwningThread.AssertOwnership(#_class " not thread-safe")
+#else // !MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+
+# define NS_DECL_OWNINGTHREAD /* nothing */
+# define NS_DECL_OWNINGEVENTTARGET /* nothing */
+# define NS_ASSERT_OWNINGTHREAD(_class) ((void)0)
+
+#endif // MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+
+// Macros for reference-count and constructor logging
+
+#if defined(NS_BUILD_REFCNT_LOGGING)
+
+# define NS_LOG_ADDREF(_p, _rc, _type, _size) \
+ NS_LogAddRef((_p), (_rc), (_type), (uint32_t)(_size))
+
+# define NS_LOG_RELEASE(_p, _rc, _type) NS_LogRelease((_p), (_rc), (_type))
+
+# define MOZ_ASSERT_CLASSNAME(_type) \
+ static_assert(std::is_class_v<_type>, \
+ "Token '" #_type "' is not a class type.")
+
+# define MOZ_ASSERT_NOT_ISUPPORTS(_type) \
+ static_assert(!std::is_base_of<nsISupports, _type>::value, \
+ "nsISupports classes don't need to call MOZ_COUNT_CTOR or " \
+ "MOZ_COUNT_DTOR");
+
+// Note that the following constructor/destructor logging macros are redundant
+// for refcounted objects that log via the NS_LOG_ADDREF/NS_LOG_RELEASE macros.
+// Refcount logging is preferred.
+# define MOZ_COUNT_CTOR(_type) \
+ do { \
+ MOZ_ASSERT_CLASSNAME(_type); \
+ MOZ_ASSERT_NOT_ISUPPORTS(_type); \
+ NS_LogCtor((void*)this, #_type, sizeof(*this)); \
+ } while (0)
+
+# define MOZ_COUNT_CTOR_INHERITED(_type, _base) \
+ do { \
+ MOZ_ASSERT_CLASSNAME(_type); \
+ MOZ_ASSERT_CLASSNAME(_base); \
+ MOZ_ASSERT_NOT_ISUPPORTS(_type); \
+ NS_LogCtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \
+ } while (0)
+
+# define MOZ_LOG_CTOR(_ptr, _name, _size) \
+ do { \
+ NS_LogCtor((void*)_ptr, _name, _size); \
+ } while (0)
+
+# define MOZ_COUNT_DTOR(_type) \
+ do { \
+ MOZ_ASSERT_CLASSNAME(_type); \
+ MOZ_ASSERT_NOT_ISUPPORTS(_type); \
+ NS_LogDtor((void*)this, #_type, sizeof(*this)); \
+ } while (0)
+
+# define MOZ_COUNT_DTOR_INHERITED(_type, _base) \
+ do { \
+ MOZ_ASSERT_CLASSNAME(_type); \
+ MOZ_ASSERT_CLASSNAME(_base); \
+ MOZ_ASSERT_NOT_ISUPPORTS(_type); \
+ NS_LogDtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \
+ } while (0)
+
+# define MOZ_LOG_DTOR(_ptr, _name, _size) \
+ do { \
+ NS_LogDtor((void*)_ptr, _name, _size); \
+ } while (0)
+
+# define MOZ_COUNTED_DEFAULT_CTOR(_type) \
+ _type() { MOZ_COUNT_CTOR(_type); }
+
+# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \
+ _prefix ~_type() _postfix { MOZ_COUNT_DTOR(_type); }
+# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) \
+ ~_type() { MOZ_COUNT_DTOR(_nestedName); }
+
+/* nsCOMPtr.h allows these macros to be defined by clients
+ * These logging functions require dynamic_cast<void*>, so they don't
+ * do anything useful if we don't have dynamic_cast<void*>.
+ * Note: The explicit comparison to nullptr is needed to avoid warnings
+ * when _p is a nullptr itself. */
+# define NSCAP_LOG_ASSIGNMENT(_c, _p) \
+ if (_p != nullptr) NS_LogCOMPtrAddRef((_c), ToSupports(_p))
+
+# define NSCAP_LOG_RELEASE(_c, _p) \
+ if (_p) NS_LogCOMPtrRelease((_c), ToSupports(_p))
+
+#else /* !NS_BUILD_REFCNT_LOGGING */
+
+# define NS_LOG_ADDREF(_p, _rc, _type, _size)
+# define NS_LOG_RELEASE(_p, _rc, _type)
+# define MOZ_COUNT_CTOR(_type)
+# define MOZ_COUNT_CTOR_INHERITED(_type, _base)
+# define MOZ_LOG_CTOR(_ptr, _name, _size)
+# define MOZ_COUNT_DTOR(_type)
+# define MOZ_COUNT_DTOR_INHERITED(_type, _base)
+# define MOZ_LOG_DTOR(_ptr, _name, _size)
+# define MOZ_COUNTED_DEFAULT_CTOR(_type) _type() = default;
+# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \
+ _prefix ~_type() _postfix = default;
+# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) ~_type() = default;
+
+#endif /* NS_BUILD_REFCNT_LOGGING */
+
+#define MOZ_COUNTED_DTOR(_type) MOZ_COUNTED_DTOR_META(_type, , )
+#define MOZ_COUNTED_DTOR_OVERRIDE(_type) \
+ MOZ_COUNTED_DTOR_META(_type, , override)
+#define MOZ_COUNTED_DTOR_FINAL(_type) MOZ_COUNTED_DTOR_META(_type, , final)
+#define MOZ_COUNTED_DTOR_VIRTUAL(_type) MOZ_COUNTED_DTOR_META(_type, virtual, )
+
+// Support for ISupports classes which interact with cycle collector.
+
+#ifdef HAVE_64BIT_BUILD
+# define NS_NUMBER_OF_FLAGS_IN_REFCNT 3
+# define NS_IS_ON_MAINTHREAD (1 << 2)
+#else
+# define NS_NUMBER_OF_FLAGS_IN_REFCNT 2
+#endif
+
+#define NS_IN_PURPLE_BUFFER (1 << 0)
+#define NS_IS_PURPLE (1 << 1)
+#define NS_REFCOUNT_CHANGE (1 << NS_NUMBER_OF_FLAGS_IN_REFCNT)
+#define NS_REFCOUNT_VALUE(_val) (_val >> NS_NUMBER_OF_FLAGS_IN_REFCNT)
+
+class nsCycleCollectingAutoRefCnt {
+ public:
+ typedef void (*Suspect)(void* aPtr, nsCycleCollectionParticipant* aCp,
+ nsCycleCollectingAutoRefCnt* aRefCnt,
+ bool* aShouldDelete);
+
+ nsCycleCollectingAutoRefCnt() : mRefCntAndFlags(0) {}
+
+ explicit nsCycleCollectingAutoRefCnt(uintptr_t aValue)
+ : mRefCntAndFlags(aValue << NS_NUMBER_OF_FLAGS_IN_REFCNT) {}
+
+ nsCycleCollectingAutoRefCnt(const nsCycleCollectingAutoRefCnt&) = delete;
+ void operator=(const nsCycleCollectingAutoRefCnt&) = delete;
+
+ MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner) {
+ return incr(aOwner, nullptr);
+ }
+
+ MOZ_ALWAYS_INLINE uintptr_t incr(void* aOwner,
+ nsCycleCollectionParticipant* aCp) {
+ mRefCntAndFlags += NS_REFCOUNT_CHANGE;
+ mRefCntAndFlags &= ~NS_IS_PURPLE;
+ // For incremental cycle collection, use the purple buffer to track objects
+ // that have been AddRef'd.
+ if (!IsInPurpleBuffer()) {
+ mRefCntAndFlags |= NS_IN_PURPLE_BUFFER;
+ // Refcount isn't zero, so Suspect won't delete anything.
+ MOZ_ASSERT(get() > 0);
+ NS_CycleCollectorSuspect3(aOwner, aCp, this, nullptr);
+ }
+ return NS_REFCOUNT_VALUE(mRefCntAndFlags);
+ }
+
+ MOZ_ALWAYS_INLINE void stabilizeForDeletion() {
+ // Set refcnt to 1 and mark us to be in the purple buffer.
+ // This way decr won't call suspect again.
+ mRefCntAndFlags = NS_REFCOUNT_CHANGE | NS_IN_PURPLE_BUFFER;
+ }
+
+ MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner,
+ bool* aShouldDelete = nullptr) {
+ return decr(aOwner, nullptr, aShouldDelete);
+ }
+
+ MOZ_ALWAYS_INLINE uintptr_t decr(void* aOwner,
+ nsCycleCollectionParticipant* aCp,
+ bool* aShouldDelete = nullptr) {
+ MOZ_ASSERT(get() > 0);
+ if (!IsInPurpleBuffer()) {
+ mRefCntAndFlags -= NS_REFCOUNT_CHANGE;
+ mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE);
+ uintptr_t retval = NS_REFCOUNT_VALUE(mRefCntAndFlags);
+ // Suspect may delete 'aOwner' and 'this'!
+ NS_CycleCollectorSuspect3(aOwner, aCp, this, aShouldDelete);
+ return retval;
+ }
+ mRefCntAndFlags -= NS_REFCOUNT_CHANGE;
+ mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE);
+ return NS_REFCOUNT_VALUE(mRefCntAndFlags);
+ }
+
+ MOZ_ALWAYS_INLINE void RemovePurple() {
+ MOZ_ASSERT(IsPurple(), "must be purple");
+ mRefCntAndFlags &= ~NS_IS_PURPLE;
+ }
+
+ MOZ_ALWAYS_INLINE void RemoveFromPurpleBuffer() {
+ MOZ_ASSERT(IsInPurpleBuffer());
+ mRefCntAndFlags &= ~(NS_IS_PURPLE | NS_IN_PURPLE_BUFFER);
+ }
+
+ MOZ_ALWAYS_INLINE bool IsPurple() const {
+ return !!(mRefCntAndFlags & NS_IS_PURPLE);
+ }
+
+ MOZ_ALWAYS_INLINE bool IsInPurpleBuffer() const {
+ return !!(mRefCntAndFlags & NS_IN_PURPLE_BUFFER);
+ }
+
+ // Cycle collected objects can only be used on a single thread, so if we ever
+ // see an object on the main thread, it will always be on the main thread.
+ // The cycle collector then uses this information to utilize faster nursery
+ // purple buffer on the main thread.
+ // The method works only on 64 bit systems.
+ MOZ_ALWAYS_INLINE void SetIsOnMainThread() {
+#ifdef HAVE_64BIT_BUILD
+ mRefCntAndFlags |= NS_IS_ON_MAINTHREAD;
+#endif
+ }
+
+#ifdef HAVE_64BIT_BUILD
+ // The NS_IS_ON_MAINTHREAD flag only exists on 64-bit builds.
+ MOZ_ALWAYS_INLINE bool IsOnMainThread() {
+ return !!(mRefCntAndFlags & NS_IS_ON_MAINTHREAD);
+ }
+#endif
+
+ MOZ_ALWAYS_INLINE nsrefcnt get() const {
+ return NS_REFCOUNT_VALUE(mRefCntAndFlags);
+ }
+
+ MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); }
+
+ private:
+ uintptr_t mRefCntAndFlags;
+};
+
+class nsAutoRefCnt {
+ public:
+ nsAutoRefCnt() : mValue(0) {}
+ explicit nsAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {}
+
+ nsAutoRefCnt(const nsAutoRefCnt&) = delete;
+ void operator=(const nsAutoRefCnt&) = delete;
+
+ // only support prefix increment/decrement
+ nsrefcnt operator++() { return ++mValue; }
+ nsrefcnt operator--() { return --mValue; }
+
+ nsrefcnt operator=(nsrefcnt aValue) { return (mValue = aValue); }
+ operator nsrefcnt() const { return mValue; }
+ nsrefcnt get() const { return mValue; }
+
+ static const bool isThreadSafe = false;
+
+ private:
+ nsrefcnt operator++(int) = delete;
+ nsrefcnt operator--(int) = delete;
+ nsrefcnt mValue;
+};
+
+namespace mozilla {
+class ThreadSafeAutoRefCnt {
+ public:
+ ThreadSafeAutoRefCnt() : mValue(0) {}
+ explicit ThreadSafeAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {}
+
+ ThreadSafeAutoRefCnt(const ThreadSafeAutoRefCnt&) = delete;
+ void operator=(const ThreadSafeAutoRefCnt&) = delete;
+
+ // only support prefix increment/decrement
+ MOZ_ALWAYS_INLINE nsrefcnt operator++() {
+ // Memory synchronization is not required when incrementing a
+ // reference count. The first increment of a reference count on a
+ // thread is not important, since the first use of the object on a
+ // thread can happen before it. What is important is the transfer
+ // of the pointer to that thread, which may happen prior to the
+ // first increment on that thread. The necessary memory
+ // synchronization is done by the mechanism that transfers the
+ // pointer between threads.
+ return mValue.fetch_add(1, std::memory_order_relaxed) + 1;
+ }
+ MOZ_ALWAYS_INLINE nsrefcnt operator--() {
+ // Since this may be the last release on this thread, we need
+ // release semantics so that prior writes on this thread are visible
+ // to the thread that destroys the object when it reads mValue with
+ // acquire semantics.
+ nsrefcnt result = mValue.fetch_sub(1, std::memory_order_release) - 1;
+ if (result == 0) {
+ // We're going to destroy the object on this thread, so we need
+ // acquire semantics to synchronize with the memory released by
+ // the last release on other threads, that is, to ensure that
+ // writes prior to that release are now visible on this thread.
+#ifdef MOZ_TSAN
+ // TSan doesn't understand std::atomic_thread_fence, so in order
+ // to avoid a false positive for every time a refcounted object
+ // is deleted, we replace the fence with an atomic operation.
+ mValue.load(std::memory_order_acquire);
+#else
+ std::atomic_thread_fence(std::memory_order_acquire);
+#endif
+ }
+ return result;
+ }
+
+ MOZ_ALWAYS_INLINE nsrefcnt operator=(nsrefcnt aValue) {
+ // Use release semantics since we're not sure what the caller is
+ // doing.
+ mValue.store(aValue, std::memory_order_release);
+ return aValue;
+ }
+ MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); }
+ MOZ_ALWAYS_INLINE nsrefcnt get() const {
+ // Use acquire semantics since we're not sure what the caller is
+ // doing.
+ return mValue.load(std::memory_order_acquire);
+ }
+
+ static const bool isThreadSafe = true;
+
+ private:
+ nsrefcnt operator++(int) = delete;
+ nsrefcnt operator--(int) = delete;
+ std::atomic<nsrefcnt> mValue;
+};
+
+namespace detail {
+
+// Type trait indicating whether a given XPCOM interface class may only be
+// implemented by types with threadsafe refcounts. This is specialized for
+// classes with the `rust_sync` annotation within XPIDL-generated header files,
+// and checked within macro-generated QueryInterface implementations.
+template <typename T>
+class InterfaceNeedsThreadSafeRefCnt : public std::false_type {};
+
+}
+} // namespace mozilla
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Declare the reference count variable and the implementations of the
+ * AddRef and QueryInterface methods.
+ */
+
+#define NS_DECL_ISUPPORTS \
+ public: \
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \
+ using HasThreadSafeRefCnt = std::false_type; \
+ \
+ protected: \
+ nsAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+#define NS_DECL_ISUPPORTS_ONEVENTTARGET \
+ public: \
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \
+ using HasThreadSafeRefCnt = std::false_type; \
+ \
+ protected: \
+ nsAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGEVENTTARGET \
+ public:
+
+#define NS_DECL_THREADSAFE_ISUPPORTS \
+ public: \
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \
+ using HasThreadSafeRefCnt = std::true_type; \
+ \
+ protected: \
+ ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS \
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(override) \
+ NS_IMETHOD_(void) DeleteCycleCollectable(void); \
+ \
+ public:
+
+#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL \
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(final) \
+ NS_IMETHOD_(void) DeleteCycleCollectable(void); \
+ \
+ public:
+
+#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(...) \
+ public: \
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) __VA_ARGS__; \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) __VA_ARGS__; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) __VA_ARGS__; \
+ using HasThreadSafeRefCnt = std::false_type; \
+ \
+ protected: \
+ nsCycleCollectingAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Implementation of AddRef and Release for non-nsISupports (ie "native")
+ * cycle-collected classes that use the purple buffer to avoid leaks.
+ */
+
+#define NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ nsrefcnt count = \
+ mRefCnt.incr(static_cast<void*>(this), \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \
+ NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \
+ return count;
+
+#define NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ nsrefcnt count = \
+ mRefCnt.decr(static_cast<void*>(this), \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ return count;
+
+#define NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(_class) \
+ NS_METHOD_(MozExternalRefCountType) _class::AddRef(void) { \
+ NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(_class, \
+ _last) \
+ NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ bool shouldDelete = false; \
+ nsrefcnt count = \
+ mRefCnt.decr(static_cast<void*>(this), \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant(), \
+ &shouldDelete); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ if (count == 0) { \
+ mRefCnt.incr(static_cast<void*>(this), \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \
+ _last; \
+ mRefCnt.decr(static_cast<void*>(this), \
+ _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \
+ if (shouldDelete) { \
+ mRefCnt.stabilizeForDeletion(); \
+ DeleteCycleCollectable(); \
+ } \
+ } \
+ return count; \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(_class) \
+ NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \
+ NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \
+ }
+
+#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(_class) \
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_)
+
+#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_VIRTUAL(_class) \
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_IMETHOD_)
+
+#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_INHERITED(_class) \
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_, \
+ override)
+
+#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, _decl, \
+ ...) \
+ public: \
+ _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__{ \
+ NS_IMPL_CC_NATIVE_ADDREF_BODY(_class)} _decl(MozExternalRefCountType) \
+ Release(void) __VA_ARGS__ { \
+ NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \
+ } \
+ using HasThreadSafeRefCnt = std::false_type; \
+ \
+ protected: \
+ nsCycleCollectingAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i>.
+ *
+ * @param _class The name of the class implementing the method
+ * @param _destroy A statement that is executed when the object's
+ * refcount drops to zero.
+ * @param _decl Name of the macro to be used for the return type of the
+ * AddRef & Release methods (typically NS_IMETHOD_ or NS_METHOD_).
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_REFCOUNTING_META(_class, _decl, _destroy, _owning, ...) \
+ public: \
+ _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ ++mRefCnt; \
+ NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \
+ return mRefCnt; \
+ } \
+ _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ --mRefCnt; \
+ NS_LOG_RELEASE(this, mRefCnt, #_class); \
+ if (mRefCnt == 0) { \
+ mRefCnt = 1; /* stabilize */ \
+ _destroy; \
+ return 0; \
+ } \
+ return mRefCnt; \
+ } \
+ using HasThreadSafeRefCnt = std::false_type; \
+ \
+ protected: \
+ nsAutoRefCnt mRefCnt; \
+ _owning public:
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i>.
+ *
+ * @param _class The name of the class implementing the method
+ * @param _destroy A statement that is executed when the object's
+ * refcount drops to zero.
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \
+ NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \
+ NS_DECL_OWNINGTHREAD, __VA_ARGS__)
+
+/**
+ * Like NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY with AddRef & Release declared
+ * virtual.
+ */
+#define NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \
+ NS_INLINE_DECL_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \
+ NS_DECL_OWNINGTHREAD, __VA_ARGS__)
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i>.
+ *
+ * @param _class The name of the class implementing the method
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_REFCOUNTING(_class, ...) \
+ NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, delete (this), __VA_ARGS__)
+
+/**
+ * Like NS_INLINE_DECL_REFCOUNTING, however the thread safety check will work
+ * with any nsISerialEventTarget. This is a workaround until bug 1648031 is
+ * properly resolved. Once this is done, it will be possible to use
+ * NS_INLINE_DECL_REFCOUNTING under all circumstances.
+ */
+#define NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(_class, ...) \
+ NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, delete (this), \
+ NS_DECL_OWNINGEVENTTARGET, __VA_ARGS__)
+
+#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, _decl, _destroy, \
+ ...) \
+ public: \
+ _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ nsrefcnt count = ++mRefCnt; \
+ NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \
+ return (nsrefcnt)count; \
+ } \
+ _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ nsrefcnt count = --mRefCnt; \
+ NS_LOG_RELEASE(this, count, #_class); \
+ if (count == 0) { \
+ _destroy; \
+ return 0; \
+ } \
+ return count; \
+ } \
+ using HasThreadSafeRefCnt = std::true_type; \
+ \
+ protected: \
+ ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \
+ \
+ public:
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i> in a threadsafe manner.
+ *
+ * DOES NOT DO REFCOUNT STABILIZATION!
+ *
+ * @param _class The name of the class implementing the method
+ * @param _destroy A statement that is executed when the object's
+ * refcount drops to zero.
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, _destroy, \
+ ...) \
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \
+ __VA_ARGS__)
+
+/**
+ * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY with AddRef & Release
+ * declared virtual.
+ */
+#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \
+ _class, _destroy, ...) \
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \
+ __VA_ARGS__)
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i> in a threadsafe manner.
+ *
+ * DOES NOT DO REFCOUNT STABILIZATION!
+ *
+ * @param _class The name of the class implementing the method
+ * @param optional override Mark the AddRef & Release methods as overrides.
+ */
+#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING(_class, ...) \
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, delete (this), \
+ __VA_ARGS__)
+
+/**
+ * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING with AddRef & Release declared
+ * virtual.
+ */
+#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(_class, ...) \
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \
+ _class, delete (this), __VA_ARGS__)
+
+#if !defined(XPCOM_GLUE_AVOID_NSPR)
+class nsISerialEventTarget;
+namespace mozilla {
+// Forward-declare `GetMainThreadSerialEventTarget`, as `nsISupportsImpl.h`
+// cannot include `nsThreadUtils.h`.
+nsISerialEventTarget* GetMainThreadSerialEventTarget();
+
+namespace detail {
+using DeleteVoidFunction = void(void*);
+void ProxyDeleteVoid(const char* aRunnableName,
+ nsISerialEventTarget* aEventTarget, void* aSelf,
+ DeleteVoidFunction* aDeleteFunc);
+} // namespace detail
+} // namespace mozilla
+
+/**
+ * Helper for _WITH_DELETE_ON_EVENT_TARGET threadsafe refcounting macros which
+ * provides an implementation of `_destroy`
+ */
+# define NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target) \
+ ::mozilla::detail::ProxyDeleteVoid( \
+ "ProxyDelete " #_class, _target, this, \
+ [](void* self) { delete static_cast<_class*>(self); })
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i> in a threadsafe manner, ensuring the
+ * destructor runs on a specific nsISerialEventTarget.
+ *
+ * DOES NOT DO REFCOUNT STABILIZATION!
+ *
+ * @param _class The name of the class implementing the method
+ * @param _target nsISerialEventTarget to run the class's destructor on
+ * @param optional override Mark the AddRef & Release methods as overrides
+ */
+# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \
+ _class, _target, ...) \
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY( \
+ _class, NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target), __VA_ARGS__)
+
+/**
+ * Use this macro to declare and implement the AddRef & Release methods for a
+ * given non-XPCOM <i>_class</i> in a threadsafe manner, ensuring the
+ * destructor runs on the main thread.
+ *
+ * DOES NOT DO REFCOUNT STABILIZATION!
+ *
+ * @param _class The name of the class implementing the method
+ * @param optional override Mark the AddRef & Release methods as overrides
+ */
+# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( \
+ _class, ...) \
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \
+ _class, ::mozilla::GetMainThreadSerialEventTarget(), __VA_ARGS__)
+#endif
+
+/**
+ * Use this macro in interface classes that you want to be able to reference
+ * using RefPtr, but don't want to provide a refcounting implemenation. The
+ * refcounting implementation can be provided by concrete subclasses that
+ * implement the interface.
+ */
+#define NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING \
+ public: \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; \
+ \
+ public:
+
+/**
+ * Use this macro to implement the AddRef method for a given <i>_class</i>
+ * @param _class The name of the class implementing the method
+ * @param _name The class name to be passed to XPCOM leak checking
+ */
+#define NS_IMPL_NAMED_ADDREF(_class, _name) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ MOZ_ASSERT(_name != nullptr, "Must specify a name"); \
+ if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \
+ nsrefcnt count = ++mRefCnt; \
+ NS_LOG_ADDREF(this, count, _name, sizeof(*this)); \
+ return count; \
+ }
+
+/**
+ * Use this macro to implement the AddRef method for a given <i>_class</i>
+ * @param _class The name of the class implementing the method
+ */
+#define NS_IMPL_ADDREF(_class) NS_IMPL_NAMED_ADDREF(_class, #_class)
+
+/**
+ * Use this macro to implement the AddRef method for a given <i>_class</i>
+ * implemented as a wholly owned aggregated object intended to implement
+ * interface(s) for its owner
+ * @param _class The name of the class implementing the method
+ * @param _aggregator the owning/containing object
+ */
+#define NS_IMPL_ADDREF_USING_AGGREGATOR(_class, _aggregator) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(_aggregator, "null aggregator"); \
+ return (_aggregator)->AddRef(); \
+ }
+
+// We decrement the refcnt before logging the actual release, but when logging
+// named things, accessing the name may not be valid after the refcnt
+// decrement, because the object may have been destroyed on a different thread.
+// Use this macro to ensure that we have a local copy of the name prior to
+// the refcnt decrement. (We use a macro to make absolutely sure the name
+// isn't loaded in builds where it wouldn't be used.)
+#ifdef NS_BUILD_REFCNT_LOGGING
+# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name) \
+ const char* const localname = _name
+#else
+# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name)
+#endif
+
+/**
+ * Use this macro to implement the Release method for a given
+ * <i>_class</i>.
+ * @param _class The name of the class implementing the method
+ * @param _name The class name to be passed to XPCOM leak checking
+ * @param _destroy A statement that is executed when the object's
+ * refcount drops to zero.
+ *
+ * For example,
+ *
+ * NS_IMPL_RELEASE_WITH_DESTROY(Foo, "Foo", Destroy(this))
+ *
+ * will cause
+ *
+ * Destroy(this);
+ *
+ * to be invoked when the object's refcount drops to zero. This
+ * allows for arbitrary teardown activity to occur (e.g., deallocation
+ * of object allocated with placement new).
+ */
+#define NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, _destroy) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ MOZ_ASSERT(_name != nullptr, "Must specify a name"); \
+ if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \
+ NS_LOAD_NAME_BEFORE_RELEASE(nametmp, _name); \
+ nsrefcnt count = --mRefCnt; \
+ NS_LOG_RELEASE(this, count, nametmp); \
+ if (count == 0) { \
+ mRefCnt = 1; /* stabilize */ \
+ _destroy; \
+ return 0; \
+ } \
+ return count; \
+ }
+
+#define NS_IMPL_RELEASE_WITH_DESTROY(_class, _destroy) \
+ NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, #_class, _destroy)
+
+/**
+ * Use this macro to implement the Release method for a given <i>_class</i>
+ * @param _class The name of the class implementing the method
+ *
+ * A note on the 'stabilization' of the refcnt to one. At that point,
+ * the object's refcount will have gone to zero. The object's
+ * destructor may trigger code that attempts to QueryInterface() and
+ * Release() 'this' again. Doing so will temporarily increment and
+ * decrement the refcount. (Only a logic error would make one try to
+ * keep a permanent hold on 'this'.) To prevent re-entering the
+ * destructor, we make sure that no balanced refcounting can return
+ * the refcount to |0|.
+ */
+#define NS_IMPL_RELEASE(_class) \
+ NS_IMPL_RELEASE_WITH_DESTROY(_class, delete (this))
+
+#define NS_IMPL_NAMED_RELEASE(_class, _name) \
+ NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, delete (this))
+
+/**
+ * Use this macro to implement the Release method for a given <i>_class</i>
+ * implemented as a wholly owned aggregated object intended to implement
+ * interface(s) for its owner
+ * @param _class The name of the class implementing the method
+ * @param _aggregator the owning/containing object
+ */
+#define NS_IMPL_RELEASE_USING_AGGREGATOR(_class, _aggregator) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(_aggregator, "null aggregator"); \
+ return (_aggregator)->Release(); \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTING_ADDREF(_class) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ nsrefcnt count = mRefCnt.incr(base); \
+ NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \
+ return count; \
+ }
+
+#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, _destroy) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ nsrefcnt count = mRefCnt.decr(base); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ return count; \
+ } \
+ NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; }
+
+#define NS_IMPL_CYCLE_COLLECTING_RELEASE(_class) \
+ NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, delete (this))
+
+// _LAST_RELEASE can be useful when certain resources should be released
+// as soon as we know the object will be deleted.
+#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(_class, _last) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ bool shouldDelete = false; \
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ if (count == 0) { \
+ mRefCnt.incr(base); \
+ _last; \
+ mRefCnt.decr(base); \
+ if (shouldDelete) { \
+ mRefCnt.stabilizeForDeletion(); \
+ DeleteCycleCollectable(); \
+ } \
+ } \
+ return count; \
+ } \
+ NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; }
+
+// This macro is same as NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE
+// except it doesn't have DeleteCycleCollectable.
+#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY( \
+ _class, _last, _destroy) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ bool shouldDelete = false; \
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ if (count == 0) { \
+ mRefCnt.incr(base); \
+ _last; \
+ mRefCnt.decr(base); \
+ if (shouldDelete) { \
+ mRefCnt.stabilizeForDeletion(); \
+ DeleteCycleCollectable(); \
+ } \
+ } \
+ return count; \
+ } \
+ NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; }
+
+// _WITH_INTERRUPTABLE_LAST_RELEASE can be useful when certain resources
+// should be released as soon as we know the object will be deleted and the
+// instance may be cached for reuse.
+// _last is performed for cleaning up its resources. Then, _maybeInterrupt is
+// tested and when it returns true, this stops deleting the instance.
+// (Note that it's not allowed to grab the instance with nsCOMPtr or RefPtr
+// during _last is performed.)
+// Therefore, when _maybeInterrupt returns true, the instance has to be grabbed
+// by nsCOMPtr or RefPtr.
+#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE( \
+ _class, _last, _maybeInterrupt) \
+ NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ bool shouldDelete = false; \
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \
+ nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \
+ NS_LOG_RELEASE(this, count, #_class); \
+ if (count == 0) { \
+ mRefCnt.incr(base); \
+ _last; \
+ mRefCnt.decr(base); \
+ if (_maybeInterrupt) { \
+ MOZ_ASSERT(mRefCnt.get() > 0); \
+ return mRefCnt.get(); \
+ } \
+ if (shouldDelete) { \
+ mRefCnt.stabilizeForDeletion(); \
+ DeleteCycleCollectable(); \
+ } \
+ } \
+ return count; \
+ } \
+ NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; }
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace mozilla::detail {
+
+// Helper which is roughly equivalent to NS_GET_IID, which also performs static
+// assertions that `Class` is allowed to implement the given XPCOM interface.
+//
+// These assertions are done like this to allow them to be used within the
+// `NS_INTERFACE_TABLE_ENTRY` macro, though they are also used in
+// `NS_IMPL_QUERY_BODY`.
+template <typename Class, typename Interface>
+constexpr const nsIID& GetImplementedIID() {
+ if constexpr (mozilla::detail::InterfaceNeedsThreadSafeRefCnt<
+ Interface>::value) {
+ static_assert(Class::HasThreadSafeRefCnt::value,
+ "Cannot implement a threadsafe interface with "
+ "non-threadsafe refcounting!");
+ }
+ return NS_GET_TEMPLATE_IID(Interface);
+}
+
+template <typename Class, typename Interface>
+constexpr const nsIID& kImplementedIID = GetImplementedIID<Class, Interface>();
+
+}
+
+/**
+ * There are two ways of implementing QueryInterface, and we use both:
+ *
+ * Table-driven QueryInterface uses a static table of IID->offset mappings
+ * and a shared helper function. Using it tends to reduce codesize and improve
+ * runtime performance (due to processor cache hits).
+ *
+ * Macro-driven QueryInterface generates a QueryInterface function directly
+ * using common macros. This is necessary if special QueryInterface features
+ * are being used (such as tearoffs and conditional interfaces).
+ *
+ * These methods can be combined into a table-driven function call followed
+ * by custom code for tearoffs and conditionals.
+ */
+
+struct QITableEntry {
+ const nsIID* iid; // null indicates end of the QITableEntry array
+ int32_t offset;
+};
+
+nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID,
+ void** aInstancePtr,
+ const QITableEntry* aEntries);
+
+/**
+ * Implement table-driven queryinterface
+ */
+
+#define NS_INTERFACE_TABLE_HEAD(_class) \
+ NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \
+ NS_ASSERTION(aInstancePtr, \
+ "QueryInterface requires a non-NULL destination!"); \
+ nsresult rv = NS_ERROR_FAILURE;
+
+#define NS_INTERFACE_TABLE_BEGIN static const QITableEntry table[] = {
+#define NS_INTERFACE_TABLE_ENTRY(_class, _interface) \
+ {&mozilla::detail::kImplementedIID<_class, _interface>, \
+ int32_t( \
+ reinterpret_cast<char*>(static_cast<_interface*>((_class*)0x1000)) - \
+ reinterpret_cast<char*>((_class*)0x1000))},
+
+#define NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(_class, _interface, _implClass) \
+ {&mozilla::detail::kImplementedIID<_class, _interface>, \
+ int32_t(reinterpret_cast<char*>(static_cast<_interface*>( \
+ static_cast<_implClass*>((_class*)0x1000))) - \
+ reinterpret_cast<char*>((_class*)0x1000))},
+
+/*
+ * XXX: we want to use mozilla::ArrayLength (or equivalent,
+ * MOZ_ARRAY_LENGTH) in this condition, but some versions of GCC don't
+ * see that the static_assert condition is actually constant in those
+ * cases, even with constexpr support (?).
+ */
+#define NS_INTERFACE_TABLE_END_WITH_PTR(_ptr) \
+ { nullptr, 0 } \
+ } \
+ ; \
+ static_assert((sizeof(table) / sizeof(table[0])) > 1, \
+ "need at least 1 interface"); \
+ rv = NS_TableDrivenQI(static_cast<void*>(_ptr), aIID, aInstancePtr, table);
+
+#define NS_INTERFACE_TABLE_END \
+ NS_INTERFACE_TABLE_END_WITH_PTR \
+ (this)
+
+#define NS_INTERFACE_TABLE_TAIL \
+ return rv; \
+ }
+
+#define NS_INTERFACE_TABLE_TAIL_INHERITING(_baseclass) \
+ if (NS_SUCCEEDED(rv)) return rv; \
+ return _baseclass::QueryInterface(aIID, aInstancePtr); \
+ }
+
+#define NS_INTERFACE_TABLE_TAIL_USING_AGGREGATOR(_aggregator) \
+ if (NS_SUCCEEDED(rv)) return rv; \
+ NS_ASSERTION(_aggregator, "null aggregator"); \
+ return _aggregator->QueryInterface(aIID, aInstancePtr) \
+ }
+
+/**
+ * This implements query interface with two assumptions: First, the
+ * class in question implements nsISupports and its own interface and
+ * nothing else. Second, the implementation of the class's primary
+ * inheritance chain leads to its own interface.
+ *
+ * @param _class The name of the class implementing the method
+ * @param _classiiddef The name of the #define symbol that defines the IID
+ * for the class (e.g. NS_ISUPPORTS_IID)
+ */
+
+#define NS_IMPL_QUERY_HEAD(_class) \
+ NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \
+ NS_ASSERTION(aInstancePtr, \
+ "QueryInterface requires a non-NULL destination!"); \
+ nsISupports* foundInterface;
+
+#define NS_IMPL_QUERY_BODY_IID(_interface) \
+ mozilla::detail::kImplementedIID<std::remove_reference_t<decltype(*this)>, \
+ _interface>
+
+#define NS_IMPL_QUERY_BODY(_interface) \
+ if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \
+ foundInterface = static_cast<_interface*>(this); \
+ else
+
+#define NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) \
+ if ((condition) && aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \
+ foundInterface = static_cast<_interface*>(this); \
+ else
+
+#define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) \
+ if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \
+ foundInterface = static_cast<_interface*>(static_cast<_implClass*>(this)); \
+ else
+
+// Use this for querying to concrete class types which cannot be unambiguously
+// cast to nsISupports. See also nsQueryObject.h.
+#define NS_IMPL_QUERY_BODY_CONCRETE(_class) \
+ if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_class))) { \
+ *aInstancePtr = do_AddRef(static_cast<_class*>(this)).take(); \
+ return NS_OK; \
+ } else
+
+#define NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) \
+ if (aIID.Equals(NS_IMPL_QUERY_BODY_IID(_interface))) \
+ foundInterface = static_cast<_interface*>(_aggregate); \
+ else
+
+#define NS_IMPL_QUERY_TAIL_GUTS \
+ foundInterface = 0; \
+ nsresult status; \
+ if (!foundInterface) { \
+ /* nsISupports should be handled by this point. If not, fail. */ \
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports))); \
+ status = NS_NOINTERFACE; \
+ } else { \
+ NS_ADDREF(foundInterface); \
+ status = NS_OK; \
+ } \
+ *aInstancePtr = foundInterface; \
+ return status; \
+ }
+
+#define NS_IMPL_QUERY_TAIL_INHERITING(_baseclass) \
+ foundInterface = 0; \
+ nsresult status; \
+ if (!foundInterface) \
+ status = _baseclass::QueryInterface(aIID, (void**)&foundInterface); \
+ else { \
+ NS_ADDREF(foundInterface); \
+ status = NS_OK; \
+ } \
+ *aInstancePtr = foundInterface; \
+ return status; \
+ }
+
+#define NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) \
+ foundInterface = 0; \
+ nsresult status; \
+ if (!foundInterface) { \
+ NS_ASSERTION(_aggregator, "null aggregator"); \
+ status = _aggregator->QueryInterface(aIID, (void**)&foundInterface); \
+ } else { \
+ NS_ADDREF(foundInterface); \
+ status = NS_OK; \
+ } \
+ *aInstancePtr = foundInterface; \
+ return status; \
+ }
+
+#define NS_IMPL_QUERY_TAIL(_supports_interface) \
+ NS_IMPL_QUERY_BODY_AMBIGUOUS(nsISupports, _supports_interface) \
+ NS_IMPL_QUERY_TAIL_GUTS
+
+/*
+ This is the new scheme. Using this notation now will allow us to switch to
+ a table driven mechanism when it's ready. Note the difference between this
+ and the (currently) underlying NS_IMPL_QUERY_INTERFACE mechanism. You must
+ explicitly mention |nsISupports| when using the interface maps.
+*/
+#define NS_INTERFACE_MAP_BEGIN(_implClass) NS_IMPL_QUERY_HEAD(_implClass)
+#define NS_INTERFACE_MAP_ENTRY(_interface) NS_IMPL_QUERY_BODY(_interface)
+#define NS_INTERFACE_MAP_ENTRY_CONDITIONAL(_interface, condition) \
+ NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition)
+#define NS_INTERFACE_MAP_ENTRY_AGGREGATED(_interface, _aggregate) \
+ NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate)
+
+#define NS_INTERFACE_MAP_END NS_IMPL_QUERY_TAIL_GUTS
+#define NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_interface, _implClass) \
+ NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass)
+#define NS_INTERFACE_MAP_ENTRY_CONCRETE(_class) \
+ NS_IMPL_QUERY_BODY_CONCRETE(_class)
+#define NS_INTERFACE_MAP_END_INHERITING(_baseClass) \
+ NS_IMPL_QUERY_TAIL_INHERITING(_baseClass)
+#define NS_INTERFACE_MAP_END_AGGREGATED(_aggregator) \
+ NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator)
+
+#define NS_INTERFACE_TABLE0(_class) \
+ NS_INTERFACE_TABLE_BEGIN \
+ NS_INTERFACE_TABLE_ENTRY(_class, nsISupports) \
+ NS_INTERFACE_TABLE_END
+
+#define NS_INTERFACE_TABLE(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments to NS_INTERFACE_TABLE"); \
+ NS_INTERFACE_TABLE_BEGIN \
+ MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(aClass, nsISupports, \
+ MOZ_ARG_1(__VA_ARGS__)) \
+ NS_INTERFACE_TABLE_END
+
+#define NS_IMPL_QUERY_INTERFACE0(_class) \
+ NS_INTERFACE_TABLE_HEAD(_class) \
+ NS_INTERFACE_TABLE0(_class) \
+ NS_INTERFACE_TABLE_TAIL
+
+#define NS_IMPL_QUERY_INTERFACE(aClass, ...) \
+ NS_INTERFACE_TABLE_HEAD(aClass) \
+ NS_INTERFACE_TABLE(aClass, __VA_ARGS__) \
+ NS_INTERFACE_TABLE_TAIL
+
+/**
+ * Declare that you're going to inherit from something that already
+ * implements nsISupports, but also implements an additional interface, thus
+ * causing an ambiguity. In this case you don't need another mRefCnt, you
+ * just need to forward the definitions to the appropriate superclass. E.g.
+ *
+ * class Bar : public Foo, public nsIBar { // both provide nsISupports
+ * public:
+ * NS_DECL_ISUPPORTS_INHERITED
+ * ...other nsIBar and Bar methods...
+ * };
+ */
+#define NS_DECL_ISUPPORTS_INHERITED \
+ public: \
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
+
+/**
+ * These macros can be used in conjunction with NS_DECL_ISUPPORTS_INHERITED
+ * to implement the nsISupports methods, forwarding the invocations to a
+ * superclass that already implements nsISupports. Don't do anything for
+ * subclasses of Runnable because it deals with subclass logging in its own
+ * way, using the mName field.
+ *
+ * Note that I didn't make these inlined because they're virtual methods.
+ */
+
+namespace mozilla {
+class Runnable;
+namespace detail {
+class SupportsThreadSafeWeakPtrBase;
+
+// Don't NS_LOG_{ADDREF,RELEASE} when inheriting from `Runnable*` or types with
+// thread safe weak references, as it will generate incorrect refcnt logs due to
+// the thread-safe `Upgrade()` call's refcount modifications not calling through
+// the derived class' `AddRef()` and `Release()` methods.
+template <typename T>
+constexpr bool ShouldLogInheritedRefcnt =
+ !std::is_convertible_v<T*, Runnable*> &&
+ !std::is_base_of_v<SupportsThreadSafeWeakPtrBase, T>;
+}
+} // namespace mozilla
+
+#define NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super) \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \
+ nsrefcnt r = Super::AddRef(); \
+ if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt<Class>) { \
+ NS_LOG_ADDREF(this, r, #Class, sizeof(*this)); \
+ } \
+ return r /* Purposefully no trailing semicolon */
+
+#define NS_IMPL_ADDREF_INHERITED(Class, Super) \
+ NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \
+ NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \
+ }
+
+#define NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super) \
+ nsrefcnt r = Super::Release(); \
+ if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt<Class>) { \
+ NS_LOG_RELEASE(this, r, #Class); \
+ } \
+ return r /* Purposefully no trailing semicolon */
+
+#define NS_IMPL_RELEASE_INHERITED(Class, Super) \
+ NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \
+ NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \
+ }
+
+/**
+ * As above but not logging the addref/release; needed if the base
+ * class might be aggregated.
+ */
+#define NS_IMPL_NONLOGGING_ADDREF_INHERITED(Class, Super) \
+ NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \
+ return Super::AddRef(); \
+ }
+
+#define NS_IMPL_NONLOGGING_RELEASE_INHERITED(Class, Super) \
+ NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \
+ return Super::Release(); \
+ }
+
+#define NS_INTERFACE_TABLE_INHERITED0(Class) /* Nothing to do here */
+
+#define NS_INTERFACE_TABLE_INHERITED(aClass, ...) \
+ static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
+ "Need more arguments to NS_INTERFACE_TABLE_INHERITED"); \
+ NS_INTERFACE_TABLE_BEGIN \
+ MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \
+ NS_INTERFACE_TABLE_END
+
+#define NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, ...) \
+ NS_INTERFACE_TABLE_HEAD(aClass) \
+ NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \
+ NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper)
+
+/**
+ * Convenience macros for implementing all nsISupports methods for
+ * a simple class.
+ * @param _class The name of the class implementing the method
+ * @param _classiiddef The name of the #define symbol that defines the IID
+ * for the class (e.g. NS_ISUPPORTS_IID)
+ */
+
+#define NS_IMPL_ISUPPORTS0(_class) \
+ NS_IMPL_ADDREF(_class) \
+ NS_IMPL_RELEASE(_class) \
+ NS_IMPL_QUERY_INTERFACE0(_class)
+
+#define NS_IMPL_ISUPPORTS(aClass, ...) \
+ NS_IMPL_ADDREF(aClass) \
+ NS_IMPL_RELEASE(aClass) \
+ NS_IMPL_QUERY_INTERFACE(aClass, __VA_ARGS__)
+
+// When possible, prefer NS_INLINE_DECL_REFCOUNTING_INHERITED to
+// NS_IMPL_ISUPPORTS_INHERITED0.
+#define NS_IMPL_ISUPPORTS_INHERITED0(aClass, aSuper) \
+ NS_INTERFACE_TABLE_HEAD(aClass) \
+ NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) \
+ NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \
+ NS_IMPL_RELEASE_INHERITED(aClass, aSuper)
+
+#define NS_IMPL_ISUPPORTS_INHERITED(aClass, aSuper, ...) \
+ NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, __VA_ARGS__) \
+ NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \
+ NS_IMPL_RELEASE_INHERITED(aClass, aSuper)
+
+/**
+ * A macro to declare and implement inherited addref/release for a class which
+ * doesn't have or need to override QueryInterface from its base class.
+ *
+ * Note: This macro always overrides the `AddRef` and `Release` methods,
+ * including when refcount logging is disabled, meaning that it will implement
+ * the `AddRef` or `Release` method from another virtual base class.
+ */
+#define NS_INLINE_DECL_REFCOUNTING_INHERITED(Class, Super) \
+ NS_IMETHOD_(MozExternalRefCountType) AddRef() override { \
+ NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \
+ } \
+ NS_IMETHOD_(MozExternalRefCountType) Release() override { \
+ NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \
+ }
+
+/*
+ * Macro to glue together a QI that starts with an interface table
+ * and segues into an interface map (e.g. it uses singleton classinfo
+ * or tearoffs).
+ */
+#define NS_INTERFACE_TABLE_TO_MAP_SEGUE \
+ if (rv == NS_OK) return rv; \
+ nsISupports* foundInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Macro to generate nsIClassInfo methods for classes which do not have
+ * corresponding nsIFactory implementations.
+ */
+#define NS_IMPL_THREADSAFE_CI(_class) \
+ NS_IMETHODIMP \
+ _class::GetInterfaces(nsTArray<nsIID>& _array) { \
+ return NS_CI_INTERFACE_GETTER_NAME(_class)(_array); \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetScriptableHelper(nsIXPCScriptable** _retval) { \
+ *_retval = nullptr; \
+ return NS_OK; \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetContractID(nsACString& _contractID) { \
+ _contractID.SetIsVoid(true); \
+ return NS_OK; \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetClassDescription(nsACString& _classDescription) { \
+ _classDescription.SetIsVoid(true); \
+ return NS_OK; \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetClassID(nsCID** _classID) { \
+ *_classID = nullptr; \
+ return NS_OK; \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetFlags(uint32_t* _flags) { \
+ *_flags = nsIClassInfo::THREADSAFE; \
+ return NS_OK; \
+ } \
+ \
+ NS_IMETHODIMP \
+ _class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc) { \
+ return NS_ERROR_NOT_AVAILABLE; \
+ }
+
+#endif
diff --git a/xpcom/base/nsISupportsUtils.h b/xpcom/base/nsISupportsUtils.h
new file mode 100644
index 0000000000..a885c5cb14
--- /dev/null
+++ b/xpcom/base/nsISupportsUtils.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsISupportsUtils_h__
+#define nsISupportsUtils_h__
+
+#include <type_traits>
+
+#include "nscore.h"
+#include "nsIOutputStream.h"
+#include "nsISupports.h"
+#include "nsError.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+
+/**
+ * Macro for adding a reference to an interface.
+ * @param _ptr The interface pointer.
+ */
+#define NS_ADDREF(_ptr) (_ptr)->AddRef()
+
+/**
+ * Macro for adding a reference to this. This macro should be used
+ * because NS_ADDREF (when tracing) may require an ambiguous cast
+ * from the pointers primary type to nsISupports. This macro sidesteps
+ * that entire problem.
+ */
+#define NS_ADDREF_THIS() AddRef()
+
+// Making this a |inline| |template| allows |aExpr| to be evaluated only once,
+// yet still denies you the ability to |AddRef()| an |nsCOMPtr|.
+template <class T>
+inline void ns_if_addref(T aExpr) {
+ if (aExpr) {
+ aExpr->AddRef();
+ }
+}
+
+/**
+ * Macro for adding a reference to an interface that checks for nullptr.
+ * @param _expr The interface pointer.
+ */
+#define NS_IF_ADDREF(_expr) ns_if_addref(_expr)
+
+/*
+ * Given these declarations, it explicitly OK and efficient to end a `getter'
+ * with:
+ *
+ * NS_IF_ADDREF(*result = mThing);
+ *
+ * even if |mThing| is an |nsCOMPtr|. If |mThing| is an |nsCOMPtr|, however, it
+ * is still _illegal_ to say |NS_IF_ADDREF(mThing)|.
+ */
+
+/**
+ * Macro for releasing a reference to an interface.
+ * @param _ptr The interface pointer.
+ */
+#define NS_RELEASE(_ptr) \
+ do { \
+ (_ptr)->Release(); \
+ (_ptr) = 0; \
+ } while (0)
+
+/**
+ * Macro for releasing a reference to this interface.
+ */
+#define NS_RELEASE_THIS() Release()
+
+/**
+ * Macro for releasing a reference to an interface, except that this
+ * macro preserves the return value from the underlying Release call.
+ * The interface pointer argument will only be NULLed if the reference count
+ * goes to zero.
+ *
+ * @param _ptr The interface pointer.
+ * @param _rc The reference count.
+ */
+#define NS_RELEASE2(_ptr, _rc) \
+ do { \
+ _rc = (_ptr)->Release(); \
+ if (0 == (_rc)) (_ptr) = 0; \
+ } while (0)
+
+/**
+ * Macro for releasing a reference to an interface that checks for nullptr;
+ * @param _ptr The interface pointer.
+ */
+#define NS_IF_RELEASE(_ptr) \
+ do { \
+ if (_ptr) { \
+ (_ptr)->Release(); \
+ (_ptr) = 0; \
+ } \
+ } while (0)
+
+/*
+ * Often you have to cast an implementation pointer, e.g., |this|, to an
+ * |nsISupports*|, but because you have multiple inheritance, a simple cast
+ * is ambiguous. One could simply say, e.g., (given a base |nsIBase|),
+ * |static_cast<nsIBase*>(this)|; but that disguises the fact that what
+ * you are really doing is disambiguating the |nsISupports|. You could make
+ * that more obvious with a double cast, e.g., |static_cast<nsISupports*>
+ (*
+ static_cast<nsIBase*>(this))|, but that is bulky and harder to read...
+ *
+ * The following macro is clean, short, and obvious. In the example above,
+ * you would use it like this: |NS_ISUPPORTS_CAST(nsIBase*, this)|.
+ */
+
+#define NS_ISUPPORTS_CAST(__unambiguousBase, __expr) \
+ static_cast<nsISupports*>(static_cast<__unambiguousBase>(__expr))
+
+// a type-safe shortcut for calling the |QueryInterface()| member function
+template <class T, class DestinationType>
+inline nsresult CallQueryInterface(T* aSource, DestinationType** aDestination) {
+ // We permit nsISupports-to-nsISupports here so that one can still obtain
+ // the canonical nsISupports pointer with CallQueryInterface.
+ static_assert(
+ !(std::is_same_v<DestinationType, T> ||
+ std::is_base_of<DestinationType, T>::value) ||
+ std::is_same_v<DestinationType, nsISupports>,
+ "don't use CallQueryInterface for compile-time-determinable casts");
+
+ MOZ_ASSERT(aSource, "null parameter");
+ MOZ_ASSERT(aDestination, "null parameter");
+
+ return aSource->QueryInterface(NS_GET_TEMPLATE_IID(DestinationType),
+ reinterpret_cast<void**>(aDestination));
+}
+
+template <class SourceType, class DestinationType>
+inline nsresult CallQueryInterface(RefPtr<SourceType>& aSourcePtr,
+ DestinationType** aDestPtr) {
+ return CallQueryInterface(aSourcePtr.get(), aDestPtr);
+}
+
+#endif /* __nsISupportsUtils_h */
diff --git a/xpcom/base/nsIUUIDGenerator.idl b/xpcom/base/nsIUUIDGenerator.idl
new file mode 100644
index 0000000000..22087adfa8
--- /dev/null
+++ b/xpcom/base/nsIUUIDGenerator.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[ptr] native nsNonConstIDPtr(nsID);
+
+/**
+ * nsIUUIDGenerator is implemented by a service that can generate
+ * universally unique identifiers, ideally using any platform-native
+ * method for generating UUIDs.
+ */
+[builtinclass, scriptable, uuid(138ad1b2-c694-41cc-b201-333ce936d8b8)]
+interface nsIUUIDGenerator : nsISupports
+{
+ /**
+ * Obtains a new UUID using appropriate platform-specific methods to
+ * obtain a nsID that can be considered to be globally unique.
+ *
+ * @returns an nsID filled in with a new UUID.
+ *
+ * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if
+ * an underlying source of randomness is not available)
+ */
+ nsIDPtr generateUUID();
+
+ /**
+ * Obtain a new UUID like the generateUUID method, but place it in
+ * the provided nsID pointer instead of allocating a new nsID.
+ *
+ * @param id an existing nsID pointer where the UUID will be stored.
+ *
+ * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if
+ * an underlying source of randomness is not available)
+ */
+ [noscript] void generateUUIDInPlace(in nsNonConstIDPtr id);
+};
diff --git a/xpcom/base/nsIVersionComparator.idl b/xpcom/base/nsIVersionComparator.idl
new file mode 100644
index 0000000000..462a07dff3
--- /dev/null
+++ b/xpcom/base/nsIVersionComparator.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Version strings are dot-separated sequences of version-parts.
+ *
+ * A version-part consists of up to four parts, all of which are optional:
+ *
+ * <number-a><string-b><number-c><string-d (everything else)>
+ *
+ * A version-part may also consist of a single asterisk "*" which indicates
+ * "infinity".
+ *
+ * Numbers are base-10, and are zero if left out.
+ * Strings are compared bytewise.
+ *
+ * For additional backwards compatibility, if "string-b" is "+" then
+ * "number-a" is incremented by 1 and "string-b" becomes "pre".
+ *
+ * 1.0pre1
+ * < 1.0pre2
+ * < 1.0 == 1.0.0 == 1.0.0.0
+ * < 1.1pre == 1.1pre0 == 1.0+
+ * < 1.1pre1a
+ * < 1.1pre1
+ * < 1.1pre10a
+ * < 1.1pre10
+ *
+ * Although not required by this interface, it is recommended that
+ * numbers remain within the limits of a signed char, i.e. -127 to 128.
+ */
+[builtinclass, scriptable, uuid(e6cd620a-edbb-41d2-9e42-9a2ffc8107f3)]
+interface nsIVersionComparator : nsISupports
+{
+ /**
+ * Compare two version strings
+ * @param A The first version
+ * @param B The second version
+ * @returns < 0 if A < B
+ * = 0 if A == B
+ * > 0 if A > B
+ */
+ long compare(in ACString A, in ACString B);
+};
diff --git a/xpcom/base/nsIWeakReference.idl b/xpcom/base/nsIWeakReference.idl
new file mode 100644
index 0000000000..723fa84344
--- /dev/null
+++ b/xpcom/base/nsIWeakReference.idl
@@ -0,0 +1,114 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+
+// For MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED.
+#include "nsDebug.h"
+
+#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED
+
+#define MOZ_WEAKREF_DECL_OWNINGTHREAD nsAutoOwningThread _mWeakRefOwningThread;
+#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD \
+ _mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe")
+#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) \
+ (that)->_mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe")
+
+#else
+
+#define MOZ_WEAKREF_DECL_OWNINGTHREAD
+#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD do { } while (false)
+#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) do { } while (false)
+
+#endif
+
+%}
+
+native MallocSizeOf(mozilla::MallocSizeOf);
+
+/**
+ * An instance of |nsIWeakReference| is a proxy object that cooperates with
+ * its referent to give clients a non-owning, non-dangling reference. Clients
+ * own the proxy, and should generally manage it with an |nsCOMPtr| (see the
+ * type |nsWeakPtr| for a |typedef| name that stands out) as they would any
+ * other XPCOM object. The |QueryReferent| member function provides a
+ * (hopefully short-lived) owning reference on demand, through which clients
+ * can get useful access to the referent, while it still exists.
+ *
+ * @version 1.0
+ * @see nsISupportsWeakReference
+ * @see nsWeakReference
+ * @see nsWeakPtr
+ */
+[scriptable, builtinclass, uuid(9188bc85-f92e-11d2-81ef-0060083a0bcf)]
+interface nsIWeakReference : nsISupports
+ {
+ /**
+ * |QueryReferent| queries the referent, if it exists, and like |QueryInterface|, produces
+ * an owning reference to the desired interface. It is designed to look and act exactly
+ * like (a proxied) |QueryInterface|. Don't hold on to the produced interface permanently;
+ * that would defeat the purpose of using a non-owning |nsIWeakReference| in the first place.
+ */
+ [binaryname(QueryReferentFromScript)]
+ void QueryReferent( in nsIIDRef uuid, [iid_is(uuid), retval] out nsQIResult result );
+
+ [notxpcom, nostdcall] size_t sizeOfOnlyThis(in MallocSizeOf aMallocSizeOf);
+%{C++
+ /**
+ * Returns true if the referring object is alive. Otherwise, false.
+ */
+ bool IsAlive() const
+ {
+ return !!mObject;
+ }
+
+ nsresult QueryReferent(const nsIID& aIID, void** aInstancePtr);
+
+protected:
+ friend class nsSupportsWeakReference;
+
+ nsIWeakReference(nsISupports* aObject)
+ : mObject(aObject)
+ {
+ }
+
+ nsIWeakReference() = delete;
+
+ MOZ_WEAKREF_DECL_OWNINGTHREAD
+
+ // The object we're holding a weak reference to.
+ nsISupports* MOZ_NON_OWNING_REF mObject;
+%}
+ };
+
+
+/**
+ * |nsISupportsWeakReference| is a factory interface which produces appropriate
+ * instances of |nsIWeakReference|. Weak references in this scheme can only be
+ * produced for objects that implement this interface.
+ *
+ * @version 1.0
+ * @see nsIWeakReference
+ * @see nsSupportsWeakReference
+ */
+[scriptable, uuid(9188bc86-f92e-11d2-81ef-0060083a0bcf)]
+interface nsISupportsWeakReference : nsISupports
+ {
+ /**
+ * |GetWeakReference| produces an appropriate instance of |nsIWeakReference|.
+ * As with all good XPCOM `getters', you own the resulting interface and should
+ * manage it with an |nsCOMPtr|.
+ *
+ * @see nsIWeakReference
+ * @see nsWeakPtr
+ * @see nsCOMPtr
+ */
+ nsIWeakReference GetWeakReference();
+ };
diff --git a/xpcom/base/nsIWeakReferenceUtils.h b/xpcom/base/nsIWeakReferenceUtils.h
new file mode 100644
index 0000000000..b76303096e
--- /dev/null
+++ b/xpcom/base/nsIWeakReferenceUtils.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIWeakReferenceUtils_h__
+#define nsIWeakReferenceUtils_h__
+
+#include "nsCOMPtr.h"
+#include "nsIWeakReference.h"
+
+typedef nsCOMPtr<nsIWeakReference> nsWeakPtr;
+
+/**
+ *
+ */
+
+// a type-safe shortcut for calling the |QueryReferent()| member function
+// T must inherit from nsIWeakReference, but the cast may be ambiguous.
+template <class T, class DestinationType>
+inline nsresult CallQueryReferent(T* aSource, DestinationType** aDestination) {
+ MOZ_ASSERT(aSource, "null parameter");
+ MOZ_ASSERT(aDestination, "null parameter");
+
+ return aSource->QueryReferent(NS_GET_TEMPLATE_IID(DestinationType),
+ reinterpret_cast<void**>(aDestination));
+}
+
+inline const nsQueryReferent do_QueryReferent(nsIWeakReference* aRawPtr,
+ nsresult* aError = 0) {
+ return nsQueryReferent(aRawPtr, aError);
+}
+
+/**
+ * Deprecated, use |do_GetWeakReference| instead.
+ */
+extern nsIWeakReference* NS_GetWeakReference(nsISupports*,
+ nsresult* aResult = 0);
+extern nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference*,
+ nsresult* aResult = 0);
+
+/**
+ * |do_GetWeakReference| is a convenience function that bundles up all the work
+ * needed to get a weak reference to an arbitrary object, i.e., the
+ * |QueryInterface|, test, and call through to |GetWeakReference|, and put it
+ * into your |nsCOMPtr|. It is specifically designed to cooperate with
+ * |nsCOMPtr| (or |nsWeakPtr|) like so: |nsWeakPtr myWeakPtr =
+ * do_GetWeakReference(aPtr);|.
+ */
+inline already_AddRefed<nsIWeakReference> do_GetWeakReference(
+ nsISupports* aRawPtr, nsresult* aError = 0) {
+ return dont_AddRef(NS_GetWeakReference(aRawPtr, aError));
+}
+
+inline already_AddRefed<nsIWeakReference> do_GetWeakReference(
+ nsISupportsWeakReference* aRawPtr, nsresult* aError = 0) {
+ return dont_AddRef(NS_GetWeakReference(aRawPtr, aError));
+}
+
+inline void do_GetWeakReference(nsIWeakReference* aRawPtr,
+ nsresult* aError = 0) {
+ // This signature exists solely to _stop_ you from doing a bad thing.
+ // Saying |do_GetWeakReference()| on a weak reference itself,
+ // is very likely to be a programmer error.
+}
+
+template <class T>
+inline void do_GetWeakReference(already_AddRefed<T>&) {
+ // This signature exists solely to _stop_ you from doing the bad thing.
+ // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by
+ // someone else is an automatic leak. See
+ // <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>.
+}
+
+template <class T>
+inline void do_GetWeakReference(already_AddRefed<T>&, nsresult*) {
+ // This signature exists solely to _stop_ you from doing the bad thing.
+ // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by
+ // someone else is an automatic leak. See
+ // <http://bugzilla.mozilla.org/show_bug.cgi?id=8221>.
+}
+
+#endif
diff --git a/xpcom/base/nsInterfaceRequestorAgg.cpp b/xpcom/base/nsInterfaceRequestorAgg.cpp
new file mode 100644
index 0000000000..490341aac2
--- /dev/null
+++ b/xpcom/base/nsInterfaceRequestorAgg.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInterfaceRequestorAgg.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+class nsInterfaceRequestorAgg final : public nsIInterfaceRequestor {
+ public:
+ // XXX This needs to support threadsafe refcounting until we fix bug 243591.
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsInterfaceRequestorAgg(nsIInterfaceRequestor* aFirst,
+ nsIInterfaceRequestor* aSecond,
+ nsIEventTarget* aConsumerTarget = nullptr)
+ : mFirst(aFirst), mSecond(aSecond), mConsumerTarget(aConsumerTarget) {
+ if (!mConsumerTarget) {
+ mConsumerTarget = mozilla::GetCurrentSerialEventTarget();
+ }
+ }
+
+ private:
+ ~nsInterfaceRequestorAgg();
+
+ nsCOMPtr<nsIInterfaceRequestor> mFirst, mSecond;
+ nsCOMPtr<nsIEventTarget> mConsumerTarget;
+};
+
+NS_IMPL_ISUPPORTS(nsInterfaceRequestorAgg, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsInterfaceRequestorAgg::GetInterface(const nsIID& aIID, void** aResult) {
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (mFirst) {
+ rv = mFirst->GetInterface(aIID, aResult);
+ }
+ if (mSecond && NS_FAILED(rv)) {
+ rv = mSecond->GetInterface(aIID, aResult);
+ }
+ return rv;
+}
+
+nsInterfaceRequestorAgg::~nsInterfaceRequestorAgg() {
+ NS_ProxyRelease("nsInterfaceRequestorAgg::mFirst", mConsumerTarget,
+ mFirst.forget());
+ NS_ProxyRelease("nsInterfaceRequestorAgg::mSecond", mConsumerTarget,
+ mSecond.forget());
+}
+
+nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst,
+ nsIInterfaceRequestor* aSecond,
+ nsIInterfaceRequestor** aResult) {
+ *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond);
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst,
+ nsIInterfaceRequestor* aSecond,
+ nsIEventTarget* aTarget,
+ nsIInterfaceRequestor** aResult) {
+ *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond, aTarget);
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
diff --git a/xpcom/base/nsInterfaceRequestorAgg.h b/xpcom/base/nsInterfaceRequestorAgg.h
new file mode 100644
index 0000000000..06c6b3d3a3
--- /dev/null
+++ b/xpcom/base/nsInterfaceRequestorAgg.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInterfaceRequestorAgg_h__
+#define nsInterfaceRequestorAgg_h__
+
+#include "nsError.h"
+
+class nsIEventTarget;
+class nsIInterfaceRequestor;
+
+/**
+ * This function returns an instance of nsIInterfaceRequestor that aggregates
+ * two nsIInterfaceRequestor instances. Its GetInterface method queries
+ * aFirst for the requested interface and will query aSecond only if aFirst
+ * failed to supply the requested interface. Both aFirst and aSecond may
+ * be null, and will be released on the main thread when the aggregator is
+ * destroyed.
+ */
+extern nsresult NS_NewInterfaceRequestorAggregation(
+ nsIInterfaceRequestor* aFirst, nsIInterfaceRequestor* aSecond,
+ nsIInterfaceRequestor** aResult);
+
+/**
+ * Like the previous method, but aFirst and aSecond will be released on the
+ * provided target thread.
+ */
+extern nsresult NS_NewInterfaceRequestorAggregation(
+ nsIInterfaceRequestor* aFirst, nsIInterfaceRequestor* aSecond,
+ nsIEventTarget* aTarget, nsIInterfaceRequestor** aResult);
+
+#endif // !defined( nsInterfaceRequestorAgg_h__ )
diff --git a/xpcom/base/nsMacPreferencesReader.h b/xpcom/base/nsMacPreferencesReader.h
new file mode 100644
index 0000000000..40d5af553a
--- /dev/null
+++ b/xpcom/base/nsMacPreferencesReader.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MacPreferencesReader_h__
+#define MacPreferencesReader_h__
+
+//-----------------------------------------------------------------------------
+
+#include "nsIMacPreferencesReader.h"
+
+#define NS_MACPREFERENCESREADER_CID \
+ { \
+ 0xb0f20595, 0x88ce, 0x4738, { \
+ 0xa1, 0xa4, 0x24, 0xde, 0x78, 0xeb, 0x80, 0x51 \
+ } \
+ }
+#define NS_MACPREFERENCESREADER_CONTRACTID \
+ "@mozilla.org/mac-preferences-reader;1"
+
+//-----------------------------------------------------------------------------
+
+class nsMacPreferencesReader : public nsIMacPreferencesReader {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACPREFERENCESREADER
+
+ nsMacPreferencesReader(){};
+
+ protected:
+ virtual ~nsMacPreferencesReader() = default;
+};
+
+#endif // MacPreferencesReader_h__
diff --git a/xpcom/base/nsMacPreferencesReader.mm b/xpcom/base/nsMacPreferencesReader.mm
new file mode 100644
index 0000000000..bfec81cd85
--- /dev/null
+++ b/xpcom/base/nsMacPreferencesReader.mm
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MacStringHelpers.h"
+#include "nsMacPreferencesReader.h"
+#include "nsString.h"
+
+#include "js/JSON.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+
+NS_IMPL_ISUPPORTS(nsMacPreferencesReader, nsIMacPreferencesReader)
+
+using namespace mozilla;
+
+static void EvaluateDict(JSONWriter* aWriter,
+ NSDictionary<NSString*, id>* aDict);
+
+static void EvaluateArray(JSONWriter* aWriter, NSArray* aArray) {
+ for (id elem in aArray) {
+ if ([elem isKindOfClass:[NSString class]]) {
+ aWriter->StringElement(MakeStringSpan([elem UTF8String]));
+ } else if ([elem isKindOfClass:[NSNumber class]]) {
+ aWriter->IntElement([elem longLongValue]);
+ } else if ([elem isKindOfClass:[NSArray class]]) {
+ aWriter->StartArrayElement();
+ EvaluateArray(aWriter, elem);
+ aWriter->EndArray();
+ } else if ([elem isKindOfClass:[NSDictionary class]]) {
+ aWriter->StartObjectElement();
+ EvaluateDict(aWriter, elem);
+ aWriter->EndObject();
+ }
+ }
+}
+
+static void EvaluateDict(JSONWriter* aWriter,
+ NSDictionary<NSString*, id>* aDict) {
+ for (NSString* key in aDict) {
+ id value = aDict[key];
+ if ([value isKindOfClass:[NSString class]]) {
+ aWriter->StringProperty(MakeStringSpan([key UTF8String]),
+ MakeStringSpan([value UTF8String]));
+ } else if ([value isKindOfClass:[NSNumber class]]) {
+ aWriter->IntProperty(MakeStringSpan([key UTF8String]),
+ [value longLongValue]);
+ } else if ([value isKindOfClass:[NSArray class]]) {
+ aWriter->StartArrayProperty(MakeStringSpan([key UTF8String]));
+ EvaluateArray(aWriter, value);
+ aWriter->EndArray();
+ } else if ([value isKindOfClass:[NSDictionary class]]) {
+ aWriter->StartObjectProperty(MakeStringSpan([key UTF8String]));
+ EvaluateDict(aWriter, value);
+ aWriter->EndObject();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMacPreferencesReader::PoliciesEnabled(bool* aPoliciesEnabled) {
+ NSString* policiesEnabledStr =
+ [NSString stringWithUTF8String:ENTERPRISE_POLICIES_ENABLED_KEY];
+ *aPoliciesEnabled = [[NSUserDefaults standardUserDefaults]
+ boolForKey:policiesEnabledStr] == YES;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacPreferencesReader::ReadPreferences(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ JSONStringWriteFunc<nsAutoCString> jsonStr;
+ JSONWriter w(jsonStr);
+ w.Start();
+ EvaluateDict(
+ &w, [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
+ w.End();
+
+ NS_ConvertUTF8toUTF16 jsonStr16(jsonStr.StringCRef());
+
+ JS::RootedValue val(aCx);
+ MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, jsonStr16.get(), jsonStr16.Length(), &val));
+
+ aResult.set(val);
+ return NS_OK;
+}
diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp
new file mode 100644
index 0000000000..86227b7aca
--- /dev/null
+++ b/xpcom/base/nsMacUtilsImpl.cpp
@@ -0,0 +1,544 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMacUtilsImpl.h"
+
+#include "base/command_line.h"
+#include "base/process_util.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Omnijar.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+
+#if defined(MOZ_SANDBOX)
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#if defined(__aarch64__)
+# include <dlfcn.h>
+#endif
+#include <sys/sysctl.h>
+
+using mozilla::StaticMutexAutoLock;
+using mozilla::Unused;
+
+#if defined(MOZ_SANDBOX) || defined(__aarch64__)
+// For thread safe setting/checking of sCachedAppPath
+static StaticMutex sCachedAppPathMutex;
+
+// Cache the appDir returned from GetAppPath to avoid doing I/O
+static StaticAutoPtr<nsCString> sCachedAppPath
+ MOZ_GUARDED_BY(sCachedAppPathMutex);
+#endif
+
+// The cached machine architectures of the .app bundle which can
+// be multiple architectures for universal binaries.
+static std::atomic<uint32_t> sBundleArchMaskAtomic = 0;
+
+#if defined(__aarch64__)
+// Limit XUL translation to one attempt
+static std::atomic<bool> sIsXULTranslated = false;
+#endif
+
+// Info.plist key associated with the developer repo path
+#define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath"
+// Info.plist key associated with the developer repo object directory
+#define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath"
+
+// Workaround this constant not being available in the macOS SDK
+#define kCFBundleExecutableArchitectureARM64 0x0100000c
+
+enum TCSMStatus { TCSM_Unknown = 0, TCSM_Available, TCSM_Unavailable };
+
+// Initialize with Unknown until we've checked if TCSM is available to set
+static Atomic<TCSMStatus> sTCSMStatus(TCSM_Unknown);
+
+#if defined(MOZ_SANDBOX) || defined(__aarch64__)
+
+// Utility method to call ClearOnShutdown() on the main thread
+static nsresult ClearCachedAppPathOnShutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ClearOnShutdown(&sCachedAppPath);
+ return NS_OK;
+}
+
+// Get the path to the .app directory (aka bundle) for the parent process.
+// When executing in the child process, this is the outer .app (such as
+// Firefox.app) and not the inner .app containing the child process
+// executable. We don't rely on the actual .app extension to allow for the
+// bundle being renamed.
+bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) {
+ StaticMutexAutoLock lock(sCachedAppPathMutex);
+ if (sCachedAppPath) {
+ aAppPath.Assign(*sCachedAppPath);
+ return true;
+ }
+
+ nsAutoCString appPath;
+ nsAutoCString appBinaryPath(
+ (CommandLine::ForCurrentProcess()->argv()[0]).c_str());
+
+ // The binary path resides within the .app dir in Contents/MacOS,
+ // e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in
+ // the binary path for the end of .app path.
+ auto pattern = "/Contents/MacOS/"_ns;
+ nsAutoCString::const_iterator start, end;
+ appBinaryPath.BeginReading(start);
+ appBinaryPath.EndReading(end);
+ if (RFindInReadable(pattern, start, end)) {
+ end = start;
+ appBinaryPath.BeginReading(start);
+
+ // If we're executing in a child process, get the parent .app path
+ // by searching backwards once more. The child executable resides
+ // in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS.
+ if (!XRE_IsParentProcess()) {
+ if (RFindInReadable(pattern, start, end)) {
+ end = start;
+ appBinaryPath.BeginReading(start);
+ } else {
+ return false;
+ }
+ }
+
+ appPath.Assign(Substring(start, end));
+ } else {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> app;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true,
+ getter_AddRefs(app));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = app->Normalize();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ app->GetNativePath(aAppPath);
+
+ if (!sCachedAppPath) {
+ sCachedAppPath = new nsCString(aAppPath);
+
+ if (NS_IsMainThread()) {
+ ClearCachedAppPathOnShutdown();
+ } else {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ClearCachedAppPathOnShutdown",
+ [] { ClearCachedAppPathOnShutdown(); }));
+ }
+ }
+
+ return true;
+}
+
+#endif /* MOZ_SANDBOX || __aarch64__ */
+
+#if defined(MOZ_SANDBOX) && defined(DEBUG)
+// If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file
+// path, return the path to the parent directory (where sibling log
+// files will be saved.)
+nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) {
+ nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG"));
+ if (bloatLog.IsEmpty()) {
+ bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG");
+ }
+ if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") {
+ return GetDirectoryPath(bloatLog.get(), aDirectoryPath);
+ }
+ return NS_OK;
+}
+
+// Given a path to a file, return the directory which contains it.
+nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath,
+ nsCString& aDirectoryPath) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = file->InitWithNativePath(nsDependentCString(aPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> directoryFile;
+ rv = file->GetParent(getter_AddRefs(directoryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directoryFile->Normalize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) {
+ MOZ_CRASH("Failed to get path for an nsIFile");
+ }
+ return NS_OK;
+}
+#endif /* MOZ_SANDBOX && DEBUG */
+
+/* static */
+bool nsMacUtilsImpl::IsTCSMAvailable() {
+ if (sTCSMStatus == TCSM_Unknown) {
+ uint32_t oldVal = 0;
+ size_t oldValSize = sizeof(oldVal);
+ int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0);
+ TCSMStatus newStatus;
+ if (rv < 0 || oldVal == 0) {
+ newStatus = TCSM_Unavailable;
+ } else {
+ newStatus = TCSM_Available;
+ }
+ // The value of sysctl kern.tcsm_available is the same for all
+ // threads within the same process. If another thread raced with us
+ // and initialized sTCSMStatus first (changing it from
+ // TCSM_Unknown), we can continue without needing to update it
+ // again. Hence, we ignore compareExchange's return value.
+ Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus);
+ }
+ return (sTCSMStatus == TCSM_Available);
+}
+
+static nsresult EnableTCSM() {
+ uint32_t newVal = 1;
+ int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal));
+ if (rv < 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+#if defined(DEBUG)
+static bool IsTCSMEnabled() {
+ uint32_t oldVal = 0;
+ size_t oldValSize = sizeof(oldVal);
+ int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0);
+ return (rv == 0) && (oldVal != 0);
+}
+#endif
+
+/*
+ * Intentionally return void so that failures will be ignored in non-debug
+ * builds. This method uses new sysctls which may not be as thoroughly tested
+ * and we don't want to cause crashes handling the failure due to an OS bug.
+ */
+/* static */
+void nsMacUtilsImpl::EnableTCSMIfAvailable() {
+ if (IsTCSMAvailable()) {
+ if (NS_FAILED(EnableTCSM())) {
+ NS_WARNING("Failed to enable TCSM");
+ }
+ MOZ_ASSERT(IsTCSMEnabled());
+ }
+}
+
+// Returns 0 on error.
+/* static */
+uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
+ uint32_t oldVal = 0;
+ size_t oldValSize = sizeof(oldVal);
+ int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0);
+ if (rv == -1) {
+ return 0;
+ }
+ return oldVal;
+}
+
+/*
+ * Helper function to read a string value for a given key from the .app's
+ * Info.plist.
+ */
+static nsresult GetStringValueFromBundlePlist(const nsAString& aKey,
+ nsAutoCString& aValue) {
+ CFBundleRef mainBundle = CFBundleGetMainBundle();
+ if (mainBundle == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Read this app's bundle Info.plist as a dictionary
+ CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle);
+ if (bundleInfoDict == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey);
+ CFStringRef key = CFStringCreateWithCString(
+ kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8);
+ if (key == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key);
+ CFRelease(key);
+ if (value == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFIndex valueLength = CFStringGetLength(value);
+ if (valueLength == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const char* valueCString =
+ CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
+ if (valueCString) {
+ aValue.Assign(valueCString);
+ return NS_OK;
+ }
+
+ CFIndex maxLength =
+ CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1;
+ char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength));
+
+ if (!CFStringGetCString(value, valueBuffer, maxLength,
+ kCFStringEncodingUTF8)) {
+ free(valueBuffer);
+ return NS_ERROR_FAILURE;
+ }
+
+ aValue.Assign(valueBuffer);
+ free(valueBuffer);
+ return NS_OK;
+}
+
+/*
+ * Helper function for reading a path string from the .app's Info.plist
+ * and returning a directory object for that path with symlinks resolved.
+ */
+static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) {
+ nsresult rv;
+
+ nsAutoCString dirPath;
+ rv = GetStringValueFromBundlePlist(aKey, dirPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dir;
+ rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false,
+ getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dir->Normalize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = dir->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory) {
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ }
+
+ dir.swap(*aDir);
+ return NS_OK;
+}
+
+nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) {
+#if defined(MOZ_SANDBOX)
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+#endif
+ return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY),
+ aRepoDir);
+}
+
+nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) {
+#if defined(MOZ_SANDBOX)
+ MOZ_ASSERT(!mozilla::IsPackagedBuild());
+#endif
+ return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY),
+ aObjDir);
+}
+
+/* static */
+nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) {
+ MOZ_ASSERT(aArchMask);
+
+ *aArchMask = sBundleArchMaskAtomic;
+ if (*aArchMask != 0) {
+ return NS_OK;
+ }
+
+ CFBundleRef mainBundle = ::CFBundleGetMainBundle();
+ if (!mainBundle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
+ if (!archList) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFIndex archCount = ::CFArrayGetCount(archList);
+ for (CFIndex i = 0; i < archCount; i++) {
+ CFNumberRef arch =
+ static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
+
+ int archInt = 0;
+ if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
+ ::CFRelease(archList);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (archInt == kCFBundleExecutableArchitecturePPC) {
+ *aArchMask |= base::PROCESS_ARCH_PPC;
+ } else if (archInt == kCFBundleExecutableArchitectureI386) {
+ *aArchMask |= base::PROCESS_ARCH_I386;
+ } else if (archInt == kCFBundleExecutableArchitecturePPC64) {
+ *aArchMask |= base::PROCESS_ARCH_PPC_64;
+ } else if (archInt == kCFBundleExecutableArchitectureX86_64) {
+ *aArchMask |= base::PROCESS_ARCH_X86_64;
+ } else if (archInt == kCFBundleExecutableArchitectureARM64) {
+ *aArchMask |= base::PROCESS_ARCH_ARM_64;
+ }
+ }
+
+ ::CFRelease(archList);
+
+ sBundleArchMaskAtomic = *aArchMask;
+
+ return NS_OK;
+}
+
+/* static */
+nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath,
+ uint32_t* aArchMask) {
+ MOZ_ASSERT(aArchMask);
+
+ *aArchMask = 0;
+
+ CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false);
+ if (!url) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url);
+ if (!archs) {
+ CFRelease(url);
+ return NS_ERROR_FAILURE;
+ }
+
+ CFIndex archCount = ::CFArrayGetCount(archs);
+ for (CFIndex i = 0; i < archCount; i++) {
+ CFNumberRef currentArch =
+ static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archs, i));
+ int currentArchInt = 0;
+ if (!::CFNumberGetValue(currentArch, kCFNumberIntType, &currentArchInt)) {
+ continue;
+ }
+ switch (currentArchInt) {
+ case kCFBundleExecutableArchitectureX86_64:
+ *aArchMask |= base::PROCESS_ARCH_X86_64;
+ break;
+ case kCFBundleExecutableArchitectureARM64:
+ *aArchMask |= base::PROCESS_ARCH_ARM_64;
+ break;
+ default:
+ break;
+ }
+ }
+
+ CFRelease(url);
+ CFRelease(archs);
+
+ // We expect x86 or ARM64 or both.
+ if (*aArchMask == 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+#if defined(__aarch64__)
+// Pre-translate XUL so that x64 child processes launched after this
+// translation will not incur the translation overhead delaying startup.
+// Returns 1 if translation is in progress, -1 on an error encountered before
+// translation, and otherwise returns the result of rosetta_translate_binaries.
+/* static */
+int nsMacUtilsImpl::PreTranslateXUL() {
+ bool expected = false;
+ if (!sIsXULTranslated.compare_exchange_strong(expected, true)) {
+ // Translation is already done or in progress.
+ return 1;
+ }
+
+ // Get the path to XUL by first getting the
+ // outer .app path and appending the path to XUL.
+ nsCString xulPath;
+ if (!GetAppPath(xulPath)) {
+ return -1;
+ }
+ xulPath.Append("/Contents/MacOS/XUL");
+
+ return PreTranslateBinary(xulPath);
+}
+
+// Use Chromium's method to pre-translate the provided binary using the
+// undocumented function "rosetta_translate_binaries" from libRosetta.dylib.
+// Re-translating the same binary does not cause translation to occur again.
+// Returns -1 on an error encountered before translation, otherwise returns
+// the rosetta_translate_binaries result. This method is partly copied from
+// Chromium code.
+/* static */
+int nsMacUtilsImpl::PreTranslateBinary(nsCString aBinaryPath) {
+ // Do not attempt to use this in child processes. Child
+ // processes executing should already be translated and
+ // sandboxing may interfere with translation.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!XRE_IsParentProcess()) {
+ return -1;
+ }
+
+ // Translation can take several seconds and therefore
+ // should not be done on the main thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (NS_IsMainThread()) {
+ return -1;
+ }
+
+ // @available() is not available for macOS 11 at this time so use
+ // -Wunguarded-availability-new to avoid compiler warnings caused
+ // by an earlier minimum SDK. ARM64 builds require the 11.0 SDK and
+ // can not be run on earlier OS versions so this is not a concern.
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunguarded-availability-new"
+ // If Rosetta is not installed, do not proceed.
+ if (!CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64)) {
+ return -1;
+ }
+# pragma clang diagnostic pop
+
+ if (aBinaryPath.IsEmpty()) {
+ return -1;
+ }
+
+ // int rosetta_translate_binaries(const char*[] paths, int npaths)
+ using rosetta_translate_binaries_t = int (*)(const char*[], int);
+
+ static auto rosetta_translate_binaries = []() {
+ void* libRosetta =
+ dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL);
+ if (!libRosetta) {
+ return static_cast<rosetta_translate_binaries_t>(nullptr);
+ }
+
+ return reinterpret_cast<rosetta_translate_binaries_t>(
+ dlsym(libRosetta, "rosetta_translate_binaries"));
+ }();
+
+ if (!rosetta_translate_binaries) {
+ return -1;
+ }
+
+ const char* pathPtr = aBinaryPath.get();
+ return rosetta_translate_binaries(&pathPtr, 1);
+}
+
+#endif
diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h
new file mode 100644
index 0000000000..9a20b539d1
--- /dev/null
+++ b/xpcom/base/nsMacUtilsImpl.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMacUtilsImpl_h___
+#define nsMacUtilsImpl_h___
+
+#include "nsString.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+
+using mozilla::Atomic;
+using mozilla::StaticAutoPtr;
+using mozilla::StaticMutex;
+
+class nsIFile;
+
+namespace nsMacUtilsImpl {
+
+// Return the repo directory and the repo object directory respectively.
+// These should only be used on Mac developer builds to determine the path
+// to the repo or object directory.
+nsresult GetRepoDir(nsIFile** aRepoDir);
+nsresult GetObjDir(nsIFile** aObjDir);
+
+#if defined(MOZ_SANDBOX) || defined(__aarch64__)
+bool GetAppPath(nsCString& aAppPath);
+#endif /* MOZ_SANDBOX || __aarch64__ */
+
+#if defined(MOZ_SANDBOX) && defined(DEBUG)
+nsresult GetBloatLogDir(nsCString& aDirectoryPath);
+nsresult GetDirectoryPath(const char* aPath, nsCString& aDirectoryPath);
+#endif /* MOZ_SANDBOX && DEBUG */
+
+void EnableTCSMIfAvailable();
+bool IsTCSMAvailable();
+uint32_t GetPhysicalCPUCount();
+nsresult GetArchitecturesForBundle(uint32_t* aArchMask);
+nsresult GetArchitecturesForBinary(const char* aPath, uint32_t* aArchMask);
+
+#if defined(__aarch64__)
+// Pre-translate binaries to avoid translation delays when launching
+// x64 child process instances for the first time. i.e. on first launch
+// after installation or after an update. Translations are cached so
+// repeated launches of the binaries do not encounter delays.
+int PreTranslateXUL();
+int PreTranslateBinary(nsCString aBinaryPath);
+#endif
+} // namespace nsMacUtilsImpl
+
+#endif /* nsMacUtilsImpl_h___ */
diff --git a/xpcom/base/nsMaybeWeakPtr.h b/xpcom/base/nsMaybeWeakPtr.h
new file mode 100644
index 0000000000..0566624265
--- /dev/null
+++ b/xpcom/base/nsMaybeWeakPtr.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMaybeWeakPtr_h_
+#define nsMaybeWeakPtr_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Try.h"
+#include "nsCOMPtr.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionNoteChild.h"
+
+// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference
+// to the template class. It's pretty minimal, but sufficient.
+
+template <class T>
+class nsMaybeWeakPtr {
+ public:
+ nsMaybeWeakPtr() = default;
+ MOZ_IMPLICIT nsMaybeWeakPtr(T* aRef) : mPtr(aRef), mWeak(false) {}
+ MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr<nsIWeakReference>& aRef)
+ : mPtr(aRef), mWeak(true) {}
+
+ nsMaybeWeakPtr<T>& operator=(T* aRef) {
+ mPtr = aRef;
+ mWeak = false;
+ return *this;
+ }
+
+ nsMaybeWeakPtr<T>& operator=(const nsCOMPtr<nsIWeakReference>& aRef) {
+ mPtr = aRef;
+ mWeak = true;
+ return *this;
+ }
+
+ bool operator==(const nsMaybeWeakPtr<T>& other) const {
+ return mPtr == other.mPtr;
+ }
+
+ nsISupports* GetRawValue() const { return mPtr.get(); }
+ bool IsWeak() const { return mWeak; }
+
+ const nsCOMPtr<T> GetValue() const;
+
+ private:
+ nsCOMPtr<nsISupports> mPtr;
+ bool mWeak;
+};
+
+// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to
+// grab a weak reference to a given object if requested. It only allows a
+// given object to appear in the array once.
+
+template <class T>
+class nsMaybeWeakPtrArray : public CopyableTArray<nsMaybeWeakPtr<T>> {
+ typedef nsTArray<nsMaybeWeakPtr<T>> MaybeWeakArray;
+
+ nsresult SetMaybeWeakPtr(nsMaybeWeakPtr<T>& aRef, T* aElement,
+ bool aOwnsWeak) {
+ nsresult rv = NS_OK;
+
+ if (aOwnsWeak) {
+ aRef = do_GetWeakReference(aElement, &rv);
+ } else {
+ aRef = aElement;
+ }
+
+ return rv;
+ }
+
+ public:
+ nsresult AppendWeakElement(T* aElement, bool aOwnsWeak) {
+ nsMaybeWeakPtr<T> ref;
+ MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));
+
+ MaybeWeakArray::AppendElement(ref);
+ return NS_OK;
+ }
+
+ nsresult AppendWeakElementUnlessExists(T* aElement, bool aOwnsWeak) {
+ nsMaybeWeakPtr<T> ref;
+ MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));
+
+ if (MaybeWeakArray::Contains(ref)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MaybeWeakArray::AppendElement(ref);
+ return NS_OK;
+ }
+
+ nsresult RemoveWeakElement(T* aElement) {
+ if (MaybeWeakArray::RemoveElement(aElement)) {
+ return NS_OK;
+ }
+
+ // Don't use do_GetWeakReference; it should only be called if we know
+ // the object supports weak references.
+ nsCOMPtr<nsISupportsWeakReference> supWeakRef = do_QueryInterface(aElement);
+ if (!supWeakRef) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIWeakReference> weakRef;
+ nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MaybeWeakArray::RemoveElement(weakRef)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_INVALID_ARG;
+ }
+};
+
+template <class T>
+const nsCOMPtr<T> nsMaybeWeakPtr<T>::GetValue() const {
+ if (!mPtr) {
+ return nullptr;
+ }
+
+ nsCOMPtr<T> ref;
+ nsresult rv;
+
+ if (mWeak) {
+ nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(mPtr);
+ if (NS_WARN_IF(!weakRef)) {
+ return nullptr;
+ }
+ ref = do_QueryReferent(weakRef, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_NULL_POINTER,
+ "QueryReferent failed with non-null pointer");
+ } else {
+ ref = do_QueryInterface(mPtr, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "QueryInterface failed with non-null pointer");
+ }
+ return ref;
+}
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(nsMaybeWeakPtrArray<T>& aField) {
+ aField.Clear();
+}
+
+template <typename E>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsMaybeWeakPtrArray<E>& aField, const char* aName, uint32_t aFlags = 0) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ size_t length = aField.Length();
+ for (size_t i = 0; i < length; ++i) {
+ CycleCollectionNoteChild(aCallback, aField[i].GetRawValue(), aName, aFlags);
+ }
+}
+
+// Call a method on each element in the array, but only if the element is
+// non-null.
+
+#define ENUMERATE_WEAKARRAY(array, type, method) \
+ for (uint32_t array_idx = 0; array_idx < array.Length(); ++array_idx) { \
+ const nsCOMPtr<type>& e = array.ElementAt(array_idx).GetValue(); \
+ if (e) e->method; \
+ }
+
+#endif
diff --git a/xpcom/base/nsMemory.h b/xpcom/base/nsMemory.h
new file mode 100644
index 0000000000..7fda7ae837
--- /dev/null
+++ b/xpcom/base/nsMemory.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMemory_h__
+#define nsMemory_h__
+
+#include "nsError.h"
+
+/**
+ *
+ * A client that wishes to be notified of low memory situations (for
+ * example, because the client maintains a large memory cache that
+ * could be released when memory is tight) should register with the
+ * observer service (see nsIObserverService) using the topic
+ * "memory-pressure". There are specific types of notifications
+ * that can occur. These types will be passed as the |aData|
+ * parameter of the of the "memory-pressure" notification:
+ *
+ * "low-memory"
+ * This will be passed as the extra data when the pressure
+ * observer is being asked to flush for low-memory conditions.
+ *
+ * "low-memory-ongoing"
+ * This will be passed when we continue to be in a low-memory
+ * condition and we want to flush caches and do other cheap
+ * forms of memory minimization, but heavy handed approaches like
+ * a GC are unlikely to succeed.
+ *
+ * "heap-minimize"
+ * This will be passed as the extra data when the pressure
+ * observer is being asked to flush because of a heap minimize
+ * call.
+ */
+
+// This is implemented in nsMemoryImpl.cpp.
+
+namespace nsMemory {
+
+/**
+ * Attempts to shrink the heap.
+ * @param immediate - if true, heap minimization will occur
+ * immediately if the call was made on the main thread. If
+ * false, the flush will be scheduled to happen when the app is
+ * idle.
+ * @throws NS_ERROR_FAILURE if 'immediate' is set and the call
+ * was not on the application's main thread.
+ */
+nsresult HeapMinimize(bool aImmediate);
+
+/**
+ * This predicate can be used to determine if the platform is a "low-memory"
+ * platform. Callers may use this to dynamically tune their behaviour
+ * to favour reduced memory usage at the expense of performance. The value
+ * returned by this function will not change over the lifetime of the process.
+ */
+bool IsLowMemoryPlatform();
+} // namespace nsMemory
+
+#endif // nsMemory_h__
diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp
new file mode 100644
index 0000000000..4996d27c7a
--- /dev/null
+++ b/xpcom/base/nsMemoryImpl.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMemory.h"
+#include "nsThreadUtils.h"
+
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIRunnable.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/Atomics.h"
+
+#ifdef ANDROID
+# include <stdio.h>
+
+// Minimum memory threshold for a device to be considered
+// a low memory platform. This value has be in sync with
+// Java's equivalent threshold, defined in HardwareUtils.java
+# define LOW_MEMORY_THRESHOLD_KB (384 * 1024)
+#endif
+
+static mozilla::Atomic<bool> sIsFlushing;
+static PRIntervalTime sLastFlushTime = 0;
+
+// static
+bool nsMemory::IsLowMemoryPlatform() {
+#ifdef ANDROID
+ static int sLowMemory =
+ -1; // initialize to unknown, lazily evaluate to 0 or 1
+ if (sLowMemory == -1) {
+ sLowMemory = 0; // assume "not low memory" in case file operations fail
+
+ // check if MemTotal from /proc/meminfo is less than LOW_MEMORY_THRESHOLD_KB
+ FILE* fd = fopen("/proc/meminfo", "r");
+ if (!fd) {
+ return false;
+ }
+ uint64_t mem = 0;
+ int rv = fscanf(fd, "MemTotal: %" PRIu64 " kB", &mem);
+ if (fclose(fd)) {
+ return false;
+ }
+ if (rv != 1) {
+ return false;
+ }
+ sLowMemory = (mem < LOW_MEMORY_THRESHOLD_KB) ? 1 : 0;
+ }
+ return (sLowMemory == 1);
+#else
+ return false;
+#endif
+}
+
+static void RunFlushers(const char16_t* aReason) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ // Instead of:
+ // os->NotifyObservers(this, "memory-pressure", aReason);
+ // we are going to do this manually to see who/what is
+ // deallocating.
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ os->EnumerateObservers("memory-pressure", getter_AddRefs(e));
+
+ if (e) {
+ nsCOMPtr<nsIObserver> observer;
+ bool loop = true;
+
+ while (NS_SUCCEEDED(e->HasMoreElements(&loop)) && loop) {
+ nsCOMPtr<nsISupports> supports;
+ e->GetNext(getter_AddRefs(supports));
+
+ if (!supports) {
+ continue;
+ }
+
+ observer = do_QueryInterface(supports);
+ observer->Observe(observer, "memory-pressure", aReason);
+ }
+ }
+ }
+
+ sIsFlushing = false;
+}
+
+static nsresult FlushMemory(const char16_t* aReason, bool aImmediate) {
+ if (aImmediate) {
+ // They've asked us to run the flusher *immediately*. We've
+ // got to be on the UI main thread for us to be able to do
+ // that...are we?
+ if (!NS_IsMainThread()) {
+ NS_ERROR("can't synchronously flush memory: not on UI thread");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ bool lastVal = sIsFlushing.exchange(true);
+ if (lastVal) {
+ return NS_OK;
+ }
+
+ PRIntervalTime now = PR_IntervalNow();
+
+ // Run the flushers immediately if we can; otherwise, proxy to the
+ // UI thread and run 'em asynchronously.
+ nsresult rv = NS_OK;
+ if (aImmediate) {
+ RunFlushers(aReason);
+ } else {
+ // Don't broadcast more than once every 1000ms to avoid being noisy
+ if (PR_IntervalToMicroseconds(now - sLastFlushTime) > 1000) {
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "FlushMemory",
+ [reason = aReason]() -> void { RunFlushers(reason); }));
+ NS_DispatchToMainThread(runnable.forget());
+ }
+ }
+
+ sLastFlushTime = now;
+ return rv;
+}
+
+nsresult nsMemory::HeapMinimize(bool aImmediate) {
+ return FlushMemory(u"heap-minimize", aImmediate);
+}
diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp
new file mode 100644
index 0000000000..28af408178
--- /dev/null
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -0,0 +1,742 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/JSONWriter.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/nsMemoryInfoDumper.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDumpUtils.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsIConsoleService.h"
+#include "nsCycleCollector.h"
+#include "nsICycleCollectorListener.h"
+#include "nsIMemoryReporter.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsGZFileWriter.h"
+#include "nsJSEnvironment.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+
+#ifdef XP_WIN
+# include <process.h>
+# ifndef getpid
+# define getpid _getpid
+# endif
+#else
+# include <unistd.h>
+#endif
+
+#ifdef XP_UNIX
+# define MOZ_SUPPORTS_FIFO 1
+#endif
+
+// Some Android devices seem to send RT signals to Firefox so we want to avoid
+// consuming those as they're not user triggered.
+#if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__))
+# define MOZ_SUPPORTS_RT_SIGNALS 1
+#endif
+
+#if defined(MOZ_SUPPORTS_RT_SIGNALS)
+# include <fcntl.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+#endif
+
+#if defined(MOZ_SUPPORTS_FIFO)
+# include "mozilla/Preferences.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+
+class DumpMemoryInfoToTempDirRunnable : public Runnable {
+ public:
+ DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize,
+ bool aMinimizeMemoryUsage)
+ : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"),
+ mIdentifier(aIdentifier),
+ mAnonymize(aAnonymize),
+ mMinimizeMemoryUsage(aMinimizeMemoryUsage) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIMemoryInfoDumper> dumper =
+ do_GetService("@mozilla.org/memory-info-dumper;1");
+ dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize,
+ mMinimizeMemoryUsage);
+ return NS_OK;
+ }
+
+ private:
+ const nsString mIdentifier;
+ const bool mAnonymize;
+ const bool mMinimizeMemoryUsage;
+};
+
+class GCAndCCLogDumpRunnable final : public Runnable,
+ public nsIDumpGCAndCCLogsCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces,
+ bool aDumpChildProcesses)
+ : mozilla::Runnable("GCAndCCLogDumpRunnable"),
+ mIdentifier(aIdentifier),
+ mDumpAllTraces(aDumpAllTraces),
+ mDumpChildProcesses(aDumpChildProcesses) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIMemoryInfoDumper> dumper =
+ do_GetService("@mozilla.org/memory-info-dumper;1");
+
+ dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces,
+ mDumpChildProcesses, this);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnFinish() override { return NS_OK; }
+
+ private:
+ ~GCAndCCLogDumpRunnable() = default;
+
+ const nsString mIdentifier;
+ const bool mDumpAllTraces;
+ const bool mDumpChildProcesses;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable,
+ nsIDumpGCAndCCLogsCallback)
+
+} // namespace
+
+#if defined(MOZ_SUPPORTS_RT_SIGNALS) // {
+namespace {
+
+/*
+ * The following code supports dumping about:memory upon receiving a signal.
+ *
+ * We listen for the following signals:
+ *
+ * - SIGRTMIN: Dump our memory reporters (and those of our child
+ * processes),
+ * - SIGRTMIN + 1: Dump our memory reporters (and those of our child
+ * processes) after minimizing memory usage, and
+ * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes.
+ *
+ * When we receive one of these signals, we write the signal number to a pipe.
+ * The IO thread then notices that the pipe has been written to, and kicks off
+ * the appropriate task on the main thread.
+ *
+ * This scheme is similar to using signalfd(), except it's portable and it
+ * doesn't require the use of sigprocmask, which is problematic because it
+ * masks signals received by child processes.
+ *
+ * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this.
+ * But that uses libevent, which does not handle the realtime signals (bug
+ * 794074).
+ */
+
+// It turns out that at least on some systems, SIGRTMIN is not a compile-time
+// constant, so these have to be set at runtime.
+static uint8_t sDumpAboutMemorySignum; // SIGRTMIN
+static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1
+static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2
+
+void doMemoryReport(const uint8_t aRecvSig) {
+ // Dump our memory reports (but run this on the main thread!).
+ bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum;
+ LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig);
+ RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
+ new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
+ /* anonymize = */ false, minimize);
+ NS_DispatchToMainThread(runnable);
+}
+
+void doGCCCDump(const uint8_t aRecvSig) {
+ LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig);
+ // Dump GC and CC logs (from the main thread).
+ RefPtr<GCAndCCLogDumpRunnable> runnable =
+ new GCAndCCLogDumpRunnable(/* identifier = */ u""_ns,
+ /* allTraces = */ true,
+ /* dumpChildProcesses = */ true);
+ NS_DispatchToMainThread(runnable);
+}
+
+} // namespace
+#endif // MOZ_SUPPORTS_RT_SIGNALS }
+
+#if defined(MOZ_SUPPORTS_FIFO) // {
+namespace {
+
+void doMemoryReport(const nsCString& aInputStr) {
+ bool minimize = aInputStr.EqualsLiteral("minimize memory report");
+ LOG("FifoWatcher(command:%s) dispatching memory report runnable.",
+ aInputStr.get());
+ RefPtr<DumpMemoryInfoToTempDirRunnable> runnable =
+ new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns,
+ /* anonymize = */ false, minimize);
+ NS_DispatchToMainThread(runnable);
+}
+
+void doGCCCDump(const nsCString& aInputStr) {
+ bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log");
+ LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.",
+ aInputStr.get());
+ RefPtr<GCAndCCLogDumpRunnable> runnable = new GCAndCCLogDumpRunnable(
+ /* identifier = */ u""_ns, doAllTracesGCCCDump,
+ /* dumpChildProcesses = */ true);
+ NS_DispatchToMainThread(runnable);
+}
+
+bool SetupFifo() {
+# ifdef DEBUG
+ static bool fifoCallbacksRegistered = false;
+# endif
+
+ if (!FifoWatcher::MaybeCreate()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!fifoCallbacksRegistered,
+ "FifoWatcher callbacks should be registered only once");
+
+ FifoWatcher* fw = FifoWatcher::GetSingleton();
+ // Dump our memory reports (but run this on the main thread!).
+ fw->RegisterCallback("memory report"_ns, doMemoryReport);
+ fw->RegisterCallback("minimize memory report"_ns, doMemoryReport);
+ // Dump GC and CC logs (from the main thread).
+ fw->RegisterCallback("gc log"_ns, doGCCCDump);
+ fw->RegisterCallback("abbreviated gc log"_ns, doGCCCDump);
+
+# ifdef DEBUG
+ fifoCallbacksRegistered = true;
+# endif
+ return true;
+}
+
+void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) {
+ LOG("%s changed", FifoWatcher::kPrefName);
+ if (SetupFifo()) {
+ Preferences::UnregisterCallback(OnFifoEnabledChange,
+ FifoWatcher::kPrefName);
+ }
+}
+
+} // namespace
+#endif // MOZ_SUPPORTS_FIFO }
+
+NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper)
+
+nsMemoryInfoDumper::nsMemoryInfoDumper() = default;
+
+nsMemoryInfoDumper::~nsMemoryInfoDumper() = default;
+
+/* static */
+void nsMemoryInfoDumper::Initialize() {
+#if defined(MOZ_SUPPORTS_RT_SIGNALS)
+ SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton();
+
+ // Dump memory reporters (and those of our child processes)
+ sDumpAboutMemorySignum = SIGRTMIN;
+ sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport);
+ // Dump our memory reporters after minimizing memory usage
+ sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1;
+ sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport);
+ // Dump the GC and CC logs in this and our child processes.
+ sGCAndCCDumpSignum = SIGRTMIN + 2;
+ sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump);
+#endif
+
+#if defined(MOZ_SUPPORTS_FIFO)
+ if (!SetupFifo()) {
+ // NB: This gets loaded early enough that it's possible there is a user pref
+ // set to enable the fifo watcher that has not been loaded yet. Register
+ // to attempt to initialize if the fifo watcher becomes enabled by
+ // a user pref.
+ Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName);
+ }
+#endif
+}
+
+static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) {
+ if (!aIdentifier.IsEmpty()) {
+ return;
+ }
+
+ // If the identifier is empty, set it to the number of whole seconds since the
+ // epoch. This identifier will appear in the files that this process
+ // generates and also the files generated by this process's children, allowing
+ // us to identify which files are from the same memory report request.
+ aIdentifier.AppendInt(static_cast<int64_t>(PR_Now()) / 1000000);
+}
+
+// Use XPCOM refcounting to fire |onFinish| when all reference-holders
+// (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself)
+// have gone away.
+class nsDumpGCAndCCLogsCallbackHolder final
+ : public nsIDumpGCAndCCLogsCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit nsDumpGCAndCCLogsCallbackHolder(
+ nsIDumpGCAndCCLogsCallback* aCallback)
+ : mCallback(aCallback) {}
+
+ NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; }
+
+ NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override {
+ return mCallback->OnDump(aGCLog, aCCLog, aIsParent);
+ }
+
+ private:
+ ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); }
+
+ nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback;
+};
+
+NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback)
+
+NS_IMETHODIMP
+nsMemoryInfoDumper::DumpGCAndCCLogsToFile(
+ const nsAString& aIdentifier, bool aDumpAllTraces, bool aDumpChildProcesses,
+ nsIDumpGCAndCCLogsCallback* aCallback) {
+ nsString identifier(aIdentifier);
+ EnsureNonEmptyIdentifier(identifier);
+ nsCOMPtr<nsIDumpGCAndCCLogsCallback> callbackHolder =
+ new nsDumpGCAndCCLogsCallbackHolder(aCallback);
+
+ if (aDumpChildProcesses) {
+ nsTArray<ContentParent*> children;
+ ContentParent::GetAll(children);
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ ContentParent* cp = children[i];
+ nsCOMPtr<nsICycleCollectorLogSink> logSink =
+ nsCycleCollector_createLogSink();
+
+ logSink->SetFilenameIdentifier(identifier);
+ logSink->SetProcessIdentifier(cp->Pid());
+
+ Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink,
+ callbackHolder);
+ }
+ }
+
+ nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
+
+ if (aDumpAllTraces) {
+ nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
+ logger->AllTraces(getter_AddRefs(allTracesLogger));
+ logger = allTracesLogger;
+ }
+
+ nsCOMPtr<nsICycleCollectorLogSink> logSink;
+ logger->GetLogSink(getter_AddRefs(logSink));
+
+ logSink->SetFilenameIdentifier(identifier);
+
+ nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
+
+ nsCOMPtr<nsIFile> gcLog, ccLog;
+ logSink->GetGcLog(getter_AddRefs(gcLog));
+ logSink->GetCcLog(getter_AddRefs(ccLog));
+ callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
+ nsICycleCollectorLogSink* aSink) {
+ nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger();
+
+ if (aDumpAllTraces) {
+ nsCOMPtr<nsICycleCollectorListener> allTracesLogger;
+ logger->AllTraces(getter_AddRefs(allTracesLogger));
+ logger = allTracesLogger;
+ }
+
+ logger->SetLogSink(aSink);
+
+ nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger);
+
+ return NS_OK;
+}
+
+static void MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
+ int aPid, const char* aSuffix, nsACString& aResult) {
+ aResult =
+ nsPrintfCString("%s-%s-%d.%s", aPrefix,
+ NS_ConvertUTF16toUTF8(aIdentifier).get(), aPid, aSuffix);
+}
+
+// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
+// the following two problems:
+// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
+// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
+class GZWriterWrapper final : public JSONWriteFunc {
+ public:
+ explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {}
+
+ void Write(const Span<const char>& aStr) final {
+ // Ignore any failure because JSONWriteFunc doesn't have a mechanism for
+ // handling errors.
+ Unused << mGZWriter->Write(aStr.data(), aStr.size());
+ }
+
+ nsresult Finish() { return mGZWriter->Finish(); }
+
+ private:
+ RefPtr<nsGZFileWriter> mGZWriter;
+};
+
+// We need two callbacks: one that handles reports, and one that is called at
+// the end of reporting. Both the callbacks need access to the same JSONWriter,
+// so we implement both of them in this one class.
+class HandleReportAndFinishReportingCallbacks final
+ : public nsIHandleReportCallback,
+ public nsIFinishReportingCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ HandleReportAndFinishReportingCallbacks(
+ UniquePtr<JSONWriter> aWriter, nsIFinishDumpingCallback* aFinishDumping,
+ nsISupports* aFinishDumpingData)
+ : mWriter(std::move(aWriter)),
+ mFinishDumping(aFinishDumping),
+ mFinishDumpingData(aFinishDumpingData) {}
+
+ // This is the callback for nsIHandleReportCallback.
+ NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
+ int32_t aKind, int32_t aUnits, int64_t aAmount,
+ const nsACString& aDescription,
+ nsISupports* aData) override {
+ nsAutoCString process;
+ if (aProcess.IsEmpty()) {
+ // If the process is empty, the report originated with the process doing
+ // the dumping. In that case, generate the process identifier, which is
+ // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
+ // don't have a process name. If we're the main process, we let
+ // $PROCESS_NAME be "Main Process".
+ //
+ // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing
+ // for live memory reports.
+ if (XRE_IsParentProcess()) {
+ // We're the main process.
+ process.AssignLiteral("Main Process");
+ } else if (ContentChild* cc = ContentChild::GetSingleton()) {
+ // Try to get the process name from ContentChild.
+ cc->GetProcessName(process);
+ }
+ ContentChild::AppendProcessId(process);
+
+ } else {
+ // Otherwise, the report originated with another process and already has a
+ // process name. Just use that.
+ process = aProcess;
+ }
+
+ mWriter->StartObjectElement();
+ {
+ mWriter->StringProperty("process", process);
+ mWriter->StringProperty("path", PromiseFlatCString(aPath));
+ mWriter->IntProperty("kind", aKind);
+ mWriter->IntProperty("units", aUnits);
+ mWriter->IntProperty("amount", aAmount);
+ mWriter->StringProperty("description", PromiseFlatCString(aDescription));
+ }
+ mWriter->EndObject();
+
+ return NS_OK;
+ }
+
+ // This is the callback for nsIFinishReportingCallback.
+ NS_IMETHOD Callback(nsISupports* aData) override {
+ mWriter->EndArray(); // end of "reports" array
+ mWriter->End();
+
+ // The call to Finish() deallocates the memory allocated by the first Write
+ // call. Because that memory was live while the memory reporters ran and
+ // was measured by them -- by "heap-allocated" if nothing else -- we want
+ // DMD to see it as well. So we deliberately don't call Finish() until
+ // after DMD finishes.
+ nsresult rv = static_cast<GZWriterWrapper&>(mWriter->WriteFunc()).Finish();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mFinishDumping) {
+ return NS_OK;
+ }
+
+ return mFinishDumping->Callback(mFinishDumpingData);
+ }
+
+ private:
+ ~HandleReportAndFinishReportingCallbacks() = default;
+
+ UniquePtr<JSONWriter> mWriter;
+ nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
+ nsCOMPtr<nsISupports> mFinishDumpingData;
+};
+
+NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
+ nsIHandleReportCallback, nsIFinishReportingCallback)
+
+class TempDirFinishCallback final : public nsIFinishDumpingCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ TempDirFinishCallback(nsIFile* aReportsTmpFile,
+ const nsCString& aReportsFinalFilename)
+ : mReportsTmpFile(aReportsTmpFile),
+ mReportsFilename(aReportsFinalFilename) {}
+
+ NS_IMETHOD Callback(nsISupports* aData) override {
+ // Rename the memory reports file, now that we're done writing all the
+ // files. Its final name is "memory-report<-identifier>-<pid>.json.gz".
+
+ nsCOMPtr<nsIFile> reportsFinalFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+ getter_AddRefs(reportsFinalFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef ANDROID
+ rv = reportsFinalFile->AppendNative("memory-reports"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ rv = reportsFinalFile->AppendNative(mReportsFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString reportsFinalFilename;
+ rv = reportsFinalFile->GetLeafName(reportsFinalFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Write a message to the console.
+
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString path;
+ mReportsTmpFile->GetPath(path);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString msg = u"nsIMemoryInfoDumper dumped reports to "_ns;
+ msg.Append(path);
+ return cs->LogStringMessage(msg.get());
+ }
+
+ private:
+ ~TempDirFinishCallback() = default;
+
+ nsCOMPtr<nsIFile> mReportsTmpFile;
+ nsCString mReportsFilename;
+};
+
+NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback)
+
+static nsresult DumpMemoryInfoToFile(nsIFile* aReportsFile,
+ nsIFinishDumpingCallback* aFinishDumping,
+ nsISupports* aFinishDumpingData,
+ bool aAnonymize, bool aMinimizeMemoryUsage,
+ nsAString& aDMDIdentifier) {
+ RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
+ nsresult rv = gzWriter->Init(aReportsFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ auto jsonWriter =
+ MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
+
+ nsCOMPtr<nsIMemoryReporterManager> mgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ // This is the first write to the file, and it causes |aWriter| to allocate
+ // over 200 KiB of memory.
+ jsonWriter->Start();
+ {
+ // Increment this number if the format changes.
+ jsonWriter->IntProperty("version", 1);
+ jsonWriter->BoolProperty("hasMozMallocUsableSize",
+ mgr->GetHasMozMallocUsableSize());
+ jsonWriter->StartArrayProperty("reports");
+ }
+
+ RefPtr<HandleReportAndFinishReportingCallbacks>
+ handleReportAndFinishReporting =
+ new HandleReportAndFinishReportingCallbacks(
+ std::move(jsonWriter), aFinishDumping, aFinishDumpingData);
+ rv = mgr->GetReportsExtended(
+ handleReportAndFinishReporting, nullptr, handleReportAndFinishReporting,
+ nullptr, aAnonymize, aMinimizeMemoryUsage, aDMDIdentifier);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMemoryInfoDumper::DumpMemoryReportsToNamedFile(
+ const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping,
+ nsISupports* aFinishDumpingData, bool aAnonymize,
+ bool aMinimizeMemoryUsage) {
+ MOZ_ASSERT(!aFilename.IsEmpty());
+
+ // Create the file.
+
+ nsCOMPtr<nsIFile> reportsFile;
+ nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ reportsFile->InitWithPath(aFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool exists;
+ rv = reportsFile->Exists(&exists);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!exists) {
+ rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsString dmdIdent;
+ return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData,
+ aAnonymize, aMinimizeMemoryUsage, dmdIdent);
+}
+
+NS_IMETHODIMP
+nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
+ bool aAnonymize,
+ bool aMinimizeMemoryUsage) {
+ nsString identifier(aIdentifier);
+ EnsureNonEmptyIdentifier(identifier);
+
+ // Open a new file named something like
+ //
+ // incomplete-memory-report-<identifier>-<pid>.json.gz
+ //
+ // in NS_OS_TEMP_DIR for writing. When we're finished writing the report,
+ // we'll rename this file and get rid of the "incomplete-" prefix.
+ //
+ // We do this because we don't want scripts which poll the filesystem
+ // looking for memory report dumps to grab a file before we're finished
+ // writing to it.
+
+ // The "unified" indicates that we merge the memory reports from all
+ // processes and write out one file, rather than a separate file for
+ // each process as was the case before bug 946407. This is so that
+ // the get_about_memory.py script in the B2G repository can
+ // determine when it's done waiting for files to appear.
+ nsCString reportsFinalFilename;
+ MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
+ reportsFinalFilename);
+
+ nsCOMPtr<nsIFile> reportsTmpFile;
+ nsresult rv;
+ // In Android case, this function will open a file named aFilename under
+ // specific folder (/data/local/tmp/memory-reports). Otherwise, it will
+ // open a file named aFilename under "NS_OS_TEMP_DIR".
+ rv = nsDumpUtils::OpenTempFile("incomplete-"_ns + reportsFinalFilename,
+ getter_AddRefs(reportsTmpFile),
+ "memory-reports"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<TempDirFinishCallback> finishDumping =
+ new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename);
+
+ return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr,
+ aAnonymize, aMinimizeMemoryUsage, identifier);
+}
+
+#ifdef MOZ_DMD
+dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton;
+
+nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
+ FILE** aOutFile) {
+ if (!dmd::IsRunning()) {
+ *aOutFile = nullptr;
+ return NS_OK;
+ }
+
+ // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
+ // if DMD is enabled.
+ nsCString dmdFilename;
+ MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
+
+ // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
+ // and dump DMD output to it. This must occur after the memory reporters
+ // have been run (above), but before the memory-reports file has been
+ // renamed (so scripts can detect the DMD file, if present).
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> dmdFile;
+ rv = nsDumpUtils::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile),
+ "memory-reports"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = dmdFile->OpenANSIFileDesc("wb", aOutFile);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed");
+
+ // Print the path, because on some platforms (e.g. Mac) it's not obvious.
+ dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get());
+
+ return rv;
+}
+
+nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) {
+ RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
+ nsresult rv = gzWriter->InitANSIFileDesc(aFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Dump DMD's memory reports analysis to the file.
+ dmd::Analyze(MakeUnique<GZWriterWrapper>(gzWriter));
+
+ rv = gzWriter->Finish();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed");
+ return rv;
+}
+#endif // MOZ_DMD
diff --git a/xpcom/base/nsMemoryInfoDumper.h b/xpcom/base/nsMemoryInfoDumper.h
new file mode 100644
index 0000000000..68f6d46de7
--- /dev/null
+++ b/xpcom/base/nsMemoryInfoDumper.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_nsMemoryInfoDumper_h
+#define mozilla_nsMemoryInfoDumper_h
+
+#include "nsIMemoryInfoDumper.h"
+#include <stdio.h>
+
+/**
+ * This class facilitates dumping information about our memory usage to disk.
+ *
+ * Its cpp file also has Linux-only code which watches various OS signals and
+ * dumps memory info upon receiving a signal. You can activate these listeners
+ * by calling Initialize().
+ */
+class nsMemoryInfoDumper : public nsIMemoryInfoDumper {
+ virtual ~nsMemoryInfoDumper();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYINFODUMPER
+
+ nsMemoryInfoDumper();
+
+ static void Initialize();
+
+#ifdef MOZ_DMD
+ // Open an appropriately named file for a DMD report. If DMD is
+ // disabled, return a null FILE* instead.
+ static nsresult OpenDMDFile(const nsAString& aIdentifier, int aPid,
+ FILE** aOutFile);
+ // Write a DMD report to the given file and close it.
+ static nsresult DumpDMDToFile(FILE* aFile);
+#endif
+};
+
+#define NS_MEMORY_INFO_DUMPER_CID \
+ { \
+ 0x00bd71fb, 0x7f09, 0x4ec3, { \
+ 0x96, 0xaf, 0xa0, 0xb5, 0x22, 0xb7, 0x79, 0x69 \
+ } \
+ }
+
+#endif
diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp
new file mode 100644
index 0000000000..aaf1baeea2
--- /dev/null
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -0,0 +1,2923 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMemoryReporterManager.h"
+
+#include "nsAtomTable.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsPrintfCString.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsITimer.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsIGlobalObject.h"
+#include "nsIXPConnect.h"
+#ifdef MOZ_GECKO_PROFILER
+# include "GeckoProfilerReporter.h"
+#endif
+#if defined(XP_UNIX) || defined(MOZ_DMD)
+# include "nsMemoryInfoDumper.h"
+#endif
+#include "nsNetCID.h"
+#include "nsThread.h"
+#include "VRProcessManager.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReportingProcess.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/MemoryReportTypes.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ipc/FileDescriptorUtils.h"
+#ifdef MOZ_PHC
+# include "PHC.h"
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/java/GeckoAppShellWrappers.h"
+# include "mozilla/jni/Utils.h"
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/MemoryInfo.h"
+
+# include <process.h>
+# ifndef getpid
+# define getpid _getpid
+# endif
+#else
+# include <unistd.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+using namespace dom;
+
+#if defined(XP_LINUX)
+
+# include "mozilla/MemoryMapping.h"
+
+# include <malloc.h>
+# include <string.h>
+# include <stdlib.h>
+
+[[nodiscard]] static nsresult GetProcSelfStatmField(int aField, int64_t* aN) {
+ // There are more than two fields, but we're only interested in the first
+ // two.
+ static const int MAX_FIELD = 2;
+ size_t fields[MAX_FIELD];
+ MOZ_ASSERT(aField < MAX_FIELD, "bad field number");
+ FILE* f = fopen("/proc/self/statm", "r");
+ if (f) {
+ int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]);
+ fclose(f);
+ if (nread == MAX_FIELD) {
+ *aN = fields[aField] * getpagesize();
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+[[nodiscard]] static nsresult GetProcSelfSmapsPrivate(int64_t* aN, pid_t aPid) {
+ // You might be tempted to calculate USS by subtracting the "shared" value
+ // from the "resident" value in /proc/<pid>/statm. But at least on Linux,
+ // statm's "shared" value actually counts pages backed by files, which has
+ // little to do with whether the pages are actually shared. /proc/self/smaps
+ // on the other hand appears to give us the correct information.
+
+ nsTArray<MemoryMapping> mappings(1024);
+ MOZ_TRY(GetMemoryMappings(mappings, aPid));
+
+ int64_t amount = 0;
+ for (auto& mapping : mappings) {
+ amount += mapping.Private_Clean();
+ amount += mapping.Private_Dirty();
+ }
+ *aN = amount;
+ return NS_OK;
+}
+
+# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
+ return GetProcSelfStatmField(0, aN);
+}
+
+[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
+ return GetProcSelfStatmField(1, aN);
+}
+
+[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmount(aN);
+}
+
+# define HAVE_RESIDENT_UNIQUE_REPORTER 1
+[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(
+ int64_t* aN, pid_t aPid = 0) {
+ return GetProcSelfSmapsPrivate(aN, aPid);
+}
+
+# ifdef HAVE_MALLINFO
+# define HAVE_SYSTEM_HEAP_REPORTER 1
+[[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) {
+ struct mallinfo info = mallinfo();
+
+ // The documentation in the glibc man page makes it sound like |uordblks|
+ // would suffice, but that only gets the small allocations that are put in
+ // the brk heap. We need |hblkhd| as well to get the larger allocations
+ // that are mmapped.
+ //
+ // The fields in |struct mallinfo| are all |int|, <sigh>, so it is
+ // unreliable if memory usage gets high. However, the system heap size on
+ // Linux should usually be zero (so long as jemalloc is enabled) so that
+ // shouldn't be a problem. Nonetheless, cast the |int|s to |size_t| before
+ // adding them to provide a small amount of extra overflow protection.
+ *aSizeOut = size_t(info.hblkhd) + size_t(info.uordblks);
+ return NS_OK;
+}
+# endif
+
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__) || defined(__FreeBSD_kernel__)
+
+# include <sys/param.h>
+# include <sys/sysctl.h>
+# if defined(__DragonFly__) || defined(__FreeBSD__) || \
+ defined(__FreeBSD_kernel__)
+# include <sys/user.h>
+# endif
+
+# include <unistd.h>
+
+# if defined(__NetBSD__)
+# undef KERN_PROC
+# define KERN_PROC KERN_PROC2
+# define KINFO_PROC struct kinfo_proc2
+# else
+# define KINFO_PROC struct kinfo_proc
+# endif
+
+# if defined(__DragonFly__)
+# define KP_SIZE(kp) (kp.kp_vm_map_size)
+# define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize())
+# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+# define KP_SIZE(kp) (kp.ki_size)
+# define KP_RSS(kp) (kp.ki_rssize * getpagesize())
+# elif defined(__NetBSD__)
+# define KP_SIZE(kp) (kp.p_vm_msize * getpagesize())
+# define KP_RSS(kp) (kp.p_vm_rssize * getpagesize())
+# elif defined(__OpenBSD__)
+# define KP_SIZE(kp) \
+ ((kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * getpagesize())
+# define KP_RSS(kp) (kp.p_vm_rssize * getpagesize())
+# endif
+
+[[nodiscard]] static nsresult GetKinfoProcSelf(KINFO_PROC* aProc) {
+# if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ static LazyLogModule sPledgeLog("SandboxPledge");
+ MOZ_LOG(sPledgeLog, LogLevel::Debug,
+ ("%s called when pledged, returning NS_ERROR_FAILURE\n", __func__));
+ return NS_ERROR_FAILURE;
+# endif
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+# if defined(__NetBSD__) || defined(__OpenBSD__)
+ sizeof(KINFO_PROC),
+ 1,
+# endif
+ };
+ u_int miblen = sizeof(mib) / sizeof(mib[0]);
+ size_t size = sizeof(KINFO_PROC);
+ if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
+ KINFO_PROC proc;
+ nsresult rv = GetKinfoProcSelf(&proc);
+ if (NS_SUCCEEDED(rv)) {
+ *aN = KP_SIZE(proc);
+ }
+ return rv;
+}
+
+[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
+ KINFO_PROC proc;
+ nsresult rv = GetKinfoProcSelf(&proc);
+ if (NS_SUCCEEDED(rv)) {
+ *aN = KP_RSS(proc);
+ }
+ return rv;
+}
+
+[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmount(aN);
+}
+
+# ifdef __FreeBSD__
+# include <libutil.h>
+# include <algorithm>
+
+[[nodiscard]] static nsresult GetKinfoVmentrySelf(int64_t* aPrss,
+ uint64_t* aMaxreg) {
+ int cnt;
+ struct kinfo_vmentry* vmmap;
+ struct kinfo_vmentry* kve;
+ if (!(vmmap = kinfo_getvmmap(getpid(), &cnt))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (aPrss) {
+ *aPrss = 0;
+ }
+ if (aMaxreg) {
+ *aMaxreg = 0;
+ }
+
+ for (int i = 0; i < cnt; i++) {
+ kve = &vmmap[i];
+ if (aPrss) {
+ *aPrss += kve->kve_private_resident;
+ }
+ if (aMaxreg) {
+ *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start);
+ }
+ }
+
+ free(vmmap);
+ return NS_OK;
+}
+
+# define HAVE_PRIVATE_REPORTER 1
+[[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) {
+ int64_t priv;
+ nsresult rv = GetKinfoVmentrySelf(&priv, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aN = priv * getpagesize();
+ return NS_OK;
+}
+
+# define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1
+[[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount(
+ int64_t* aN) {
+ uint64_t biggestRegion;
+ nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion);
+ if (NS_SUCCEEDED(rv)) {
+ *aN = biggestRegion;
+ }
+ return NS_OK;
+}
+# endif // FreeBSD
+
+#elif defined(SOLARIS)
+
+# include <procfs.h>
+# include <fcntl.h>
+# include <unistd.h>
+
+static void XMappingIter(int64_t& aVsize, int64_t& aResident,
+ int64_t& aShared) {
+ aVsize = -1;
+ aResident = -1;
+ aShared = -1;
+ int mapfd = open("/proc/self/xmap", O_RDONLY);
+ struct stat st;
+ prxmap_t* prmapp = nullptr;
+ if (mapfd >= 0) {
+ if (!fstat(mapfd, &st)) {
+ int nmap = st.st_size / sizeof(prxmap_t);
+ while (1) {
+ // stat(2) on /proc/<pid>/xmap returns an incorrect value,
+ // prior to the release of Solaris 11.
+ // Here is a workaround for it.
+ nmap *= 2;
+ prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t));
+ if (!prmapp) {
+ // out of memory
+ break;
+ }
+ int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0);
+ if (n < 0) {
+ break;
+ }
+ if (nmap >= n / sizeof(prxmap_t)) {
+ aVsize = 0;
+ aResident = 0;
+ aShared = 0;
+ for (int i = 0; i < n / sizeof(prxmap_t); i++) {
+ aVsize += prmapp[i].pr_size;
+ aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize;
+ if (prmapp[i].pr_mflags & MA_SHARED) {
+ aShared += prmapp[i].pr_rss * prmapp[i].pr_pagesize;
+ }
+ }
+ break;
+ }
+ free(prmapp);
+ }
+ free(prmapp);
+ }
+ close(mapfd);
+ }
+}
+
+# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
+ int64_t vsize, resident, shared;
+ XMappingIter(vsize, resident, shared);
+ if (vsize == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = vsize;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
+ int64_t vsize, resident, shared;
+ XMappingIter(vsize, resident, shared);
+ if (resident == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = resident;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmount(aN);
+}
+
+# define HAVE_RESIDENT_UNIQUE_REPORTER 1
+[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) {
+ int64_t vsize, resident, shared;
+ XMappingIter(vsize, resident, shared);
+ if (resident == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = resident - shared;
+ return NS_OK;
+}
+
+#elif defined(XP_MACOSX)
+
+# include <mach/mach_init.h>
+# include <mach/mach_vm.h>
+# include <mach/shared_region.h>
+# include <mach/task.h>
+# include <sys/sysctl.h>
+
+[[nodiscard]] static bool GetTaskBasicInfo(struct task_basic_info* aTi) {
+ mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
+ kern_return_t kr =
+ task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)aTi, &count);
+ return kr == KERN_SUCCESS;
+}
+
+// The VSIZE figure on Mac includes huge amounts of shared memory and is always
+// absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report
+// it, so we might as well too.
+# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
+ task_basic_info ti;
+ if (!GetTaskBasicInfo(&ti)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = ti.virtual_size;
+ return NS_OK;
+}
+
+// If we're using jemalloc on Mac, we need to instruct jemalloc to purge the
+// pages it has madvise(MADV_FREE)'d before we read our RSS in order to get
+// an accurate result. The OS will take away MADV_FREE'd pages when there's
+// memory pressure, so ideally, they shouldn't count against our RSS.
+//
+// Purging these pages can take a long time for some users (see bug 789975),
+// so we provide the option to get the RSS without purging first.
+[[nodiscard]] static nsresult ResidentDistinguishedAmountHelper(int64_t* aN,
+ bool aDoPurge) {
+# ifdef HAVE_JEMALLOC_STATS
+ if (aDoPurge) {
+ Telemetry::AutoTimer<Telemetry::MEMORY_FREE_PURGED_PAGES_MS> timer;
+ jemalloc_purge_freed_pages();
+ }
+# endif
+
+ task_basic_info ti;
+ if (!GetTaskBasicInfo(&ti)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = ti.resident_size;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false);
+}
+
+[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true);
+}
+
+# define HAVE_RESIDENT_UNIQUE_REPORTER 1
+
+static bool InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) {
+ mach_vm_address_t base;
+ mach_vm_address_t size;
+
+ switch (aType) {
+ case CPU_TYPE_ARM:
+ base = SHARED_REGION_BASE_ARM;
+ size = SHARED_REGION_SIZE_ARM;
+ break;
+ case CPU_TYPE_ARM64:
+ base = SHARED_REGION_BASE_ARM64;
+ size = SHARED_REGION_SIZE_ARM64;
+ break;
+ case CPU_TYPE_I386:
+ base = SHARED_REGION_BASE_I386;
+ size = SHARED_REGION_SIZE_I386;
+ break;
+ case CPU_TYPE_X86_64:
+ base = SHARED_REGION_BASE_X86_64;
+ size = SHARED_REGION_SIZE_X86_64;
+ break;
+ default:
+ return false;
+ }
+
+ return base <= aAddr && aAddr < (base + size);
+}
+
+[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(
+ int64_t* aN, mach_port_t aPort = 0) {
+ if (!aN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ cpu_type_t cpu_type;
+ size_t len = sizeof(cpu_type);
+ if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Roughly based on libtop_update_vm_regions in
+ // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c
+ size_t privatePages = 0;
+ mach_vm_size_t topSize = 0;
+ for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS;; addr += topSize) {
+ vm_region_top_info_data_t topInfo;
+ mach_msg_type_number_t topInfoCount = VM_REGION_TOP_INFO_COUNT;
+ mach_port_t topObjectName;
+
+ kern_return_t kr = mach_vm_region(
+ aPort ? aPort : mach_task_self(), &addr, &topSize, VM_REGION_TOP_INFO,
+ reinterpret_cast<vm_region_info_t>(&topInfo), &topInfoCount,
+ &topObjectName);
+ if (kr == KERN_INVALID_ADDRESS) {
+ // Done iterating VM regions.
+ break;
+ } else if (kr != KERN_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (InSharedRegion(addr, cpu_type) && topInfo.share_mode != SM_PRIVATE) {
+ continue;
+ }
+
+ switch (topInfo.share_mode) {
+ case SM_LARGE_PAGE:
+ // NB: Large pages are not shareable and always resident.
+ case SM_PRIVATE:
+ privatePages += topInfo.private_pages_resident;
+ privatePages += topInfo.shared_pages_resident;
+ break;
+ case SM_COW:
+ privatePages += topInfo.private_pages_resident;
+ if (topInfo.ref_count == 1) {
+ // Treat copy-on-write pages as private if they only have one
+ // reference.
+ privatePages += topInfo.shared_pages_resident;
+ }
+ break;
+ case SM_SHARED: {
+ // Using mprotect() or similar to protect a page in the middle of a
+ // mapping can create aliased mappings. They look like shared mappings
+ // to the VM_REGION_TOP_INFO interface, so re-check with
+ // VM_REGION_EXTENDED_INFO.
+
+ mach_vm_size_t exSize = 0;
+ vm_region_extended_info_data_t exInfo;
+ mach_msg_type_number_t exInfoCount = VM_REGION_EXTENDED_INFO_COUNT;
+ mach_port_t exObjectName;
+ kr = mach_vm_region(aPort ? aPort : mach_task_self(), &addr, &exSize,
+ VM_REGION_EXTENDED_INFO,
+ reinterpret_cast<vm_region_info_t>(&exInfo),
+ &exInfoCount, &exObjectName);
+ if (kr == KERN_INVALID_ADDRESS) {
+ // Done iterating VM regions.
+ break;
+ } else if (kr != KERN_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (exInfo.share_mode == SM_PRIVATE_ALIASED) {
+ privatePages += exInfo.pages_resident;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ vm_size_t pageSize;
+ if (host_page_size(aPort ? aPort : mach_task_self(), &pageSize) !=
+ KERN_SUCCESS) {
+ pageSize = PAGE_SIZE;
+ }
+
+ *aN = privatePages * pageSize;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult PhysicalFootprintAmount(int64_t* aN,
+ mach_port_t aPort = 0) {
+ MOZ_ASSERT(aN);
+
+ // The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data
+ // matches the value in the 'Memory' column of the Activity Monitor.
+ task_vm_info_data_t task_vm_info;
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
+ kern_return_t kr = task_info(aPort ? aPort : mach_task_self(), TASK_VM_INFO,
+ (task_info_t)&task_vm_info, &count);
+ if (kr != KERN_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aN = task_vm_info.phys_footprint;
+ return NS_OK;
+}
+
+#elif defined(XP_WIN)
+
+# include <windows.h>
+# include <psapi.h>
+# include <algorithm>
+
+# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) {
+ MEMORYSTATUSEX s;
+ s.dwLength = sizeof(s);
+
+ if (!GlobalMemoryStatusEx(&s)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aN = s.ullTotalVirtual - s.ullAvailVirtual;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) {
+ PROCESS_MEMORY_COUNTERS pmc;
+ pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
+
+ if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aN = pmc.WorkingSetSize;
+ return NS_OK;
+}
+
+[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) {
+ return ResidentDistinguishedAmount(aN);
+}
+
+# define HAVE_RESIDENT_UNIQUE_REPORTER 1
+
+[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(
+ int64_t* aN, HANDLE aProcess = nullptr) {
+ // Determine how many entries we need.
+ PSAPI_WORKING_SET_INFORMATION tmp;
+ DWORD tmpSize = sizeof(tmp);
+ memset(&tmp, 0, tmpSize);
+
+ HANDLE proc = aProcess ? aProcess : GetCurrentProcess();
+ QueryWorkingSet(proc, &tmp, tmpSize);
+
+ // Fudge the size in case new entries are added between calls.
+ size_t entries = tmp.NumberOfEntries * 2;
+
+ if (!entries) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD infoArraySize = tmpSize + (entries * sizeof(PSAPI_WORKING_SET_BLOCK));
+ UniqueFreePtr<PSAPI_WORKING_SET_INFORMATION> infoArray(
+ static_cast<PSAPI_WORKING_SET_INFORMATION*>(malloc(infoArraySize)));
+
+ if (!infoArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!QueryWorkingSet(proc, infoArray.get(), infoArraySize)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ entries = static_cast<size_t>(infoArray->NumberOfEntries);
+ size_t privatePages = 0;
+ for (size_t i = 0; i < entries; i++) {
+ // Count shared pages that only one process is using as private.
+ if (!infoArray->WorkingSetInfo[i].Shared ||
+ infoArray->WorkingSetInfo[i].ShareCount <= 1) {
+ privatePages++;
+ }
+ }
+
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+
+ *aN = privatePages * si.dwPageSize;
+ return NS_OK;
+}
+
+# define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1
+[[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount(
+ int64_t* aN) {
+ SIZE_T biggestRegion = 0;
+ MEMORY_BASIC_INFORMATION vmemInfo = {0};
+ for (size_t currentAddress = 0;;) {
+ if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) {
+ // Something went wrong, just return whatever we've got already.
+ break;
+ }
+
+ if (vmemInfo.State == MEM_FREE) {
+ biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize);
+ }
+
+ SIZE_T lastAddress = currentAddress;
+ currentAddress += vmemInfo.RegionSize;
+
+ // If we overflow, we've examined all of the address space.
+ if (currentAddress < lastAddress) {
+ break;
+ }
+ }
+
+ *aN = biggestRegion;
+ return NS_OK;
+}
+
+# define HAVE_PRIVATE_REPORTER 1
+[[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) {
+ PROCESS_MEMORY_COUNTERS_EX pmcex;
+ pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
+
+ if (!GetProcessMemoryInfo(GetCurrentProcess(),
+ (PPROCESS_MEMORY_COUNTERS)&pmcex, sizeof(pmcex))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aN = pmcex.PrivateUsage;
+ return NS_OK;
+}
+
+# define HAVE_SYSTEM_HEAP_REPORTER 1
+// Windows can have multiple separate heaps, but we should not touch non-default
+// heaps because they may be destroyed at anytime while we hold a handle. So we
+// count only the default heap.
+[[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) {
+ HANDLE heap = GetProcessHeap();
+
+ NS_ENSURE_TRUE(HeapLock(heap), NS_ERROR_FAILURE);
+
+ int64_t heapSize = 0;
+ PROCESS_HEAP_ENTRY entry;
+ entry.lpData = nullptr;
+ while (HeapWalk(heap, &entry)) {
+ // We don't count entry.cbOverhead, because we just want to measure the
+ // space available to the program.
+ if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) {
+ heapSize += entry.cbData;
+ }
+ }
+
+ // Check this result only after unlocking the heap, so that we don't leave
+ // the heap locked if there was an error.
+ DWORD lastError = GetLastError();
+
+ // I have no idea how things would proceed if unlocking this heap failed...
+ NS_ENSURE_TRUE(HeapUnlock(heap), NS_ERROR_FAILURE);
+
+ NS_ENSURE_TRUE(lastError == ERROR_NO_MORE_ITEMS, NS_ERROR_FAILURE);
+
+ *aSizeOut = heapSize;
+ return NS_OK;
+}
+
+struct SegmentKind {
+ DWORD mState;
+ DWORD mType;
+ DWORD mProtect;
+ int mIsStack;
+};
+
+struct SegmentEntry : public PLDHashEntryHdr {
+ static PLDHashNumber HashKey(const void* aKey) {
+ auto kind = static_cast<const SegmentKind*>(aKey);
+ return mozilla::HashGeneric(kind->mState, kind->mType, kind->mProtect,
+ kind->mIsStack);
+ }
+
+ static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) {
+ auto kind = static_cast<const SegmentKind*>(aKey);
+ auto entry = static_cast<const SegmentEntry*>(aEntry);
+ return kind->mState == entry->mKind.mState &&
+ kind->mType == entry->mKind.mType &&
+ kind->mProtect == entry->mKind.mProtect &&
+ kind->mIsStack == entry->mKind.mIsStack;
+ }
+
+ static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey) {
+ auto kind = static_cast<const SegmentKind*>(aKey);
+ auto entry = static_cast<SegmentEntry*>(aEntry);
+ entry->mKind = *kind;
+ entry->mCount = 0;
+ entry->mSize = 0;
+ }
+
+ static const PLDHashTableOps Ops;
+
+ SegmentKind mKind; // The segment kind.
+ uint32_t mCount; // The number of segments of this kind.
+ size_t mSize; // The combined size of segments of this kind.
+};
+
+/* static */ const PLDHashTableOps SegmentEntry::Ops = {
+ SegmentEntry::HashKey, SegmentEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub,
+ SegmentEntry::InitEntry};
+
+class WindowsAddressSpaceReporter final : public nsIMemoryReporter {
+ ~WindowsAddressSpaceReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ // First iterate over all the segments and record how many of each kind
+ // there were and their aggregate sizes. We use a hash table for this
+ // because there are a couple of dozen different kinds possible.
+
+ PLDHashTable table(&SegmentEntry::Ops, sizeof(SegmentEntry));
+ MEMORY_BASIC_INFORMATION info = {0};
+ bool isPrevSegStackGuard = false;
+ for (size_t currentAddress = 0;;) {
+ if (!VirtualQuery((LPCVOID)currentAddress, &info, sizeof(info))) {
+ // Something went wrong, just return whatever we've got already.
+ break;
+ }
+
+ size_t size = info.RegionSize;
+
+ // Note that |type| and |protect| are ignored in some cases.
+ DWORD state = info.State;
+ DWORD type =
+ (state == MEM_RESERVE || state == MEM_COMMIT) ? info.Type : 0;
+ DWORD protect = (state == MEM_COMMIT) ? info.Protect : 0;
+ bool isStack = isPrevSegStackGuard && state == MEM_COMMIT &&
+ type == MEM_PRIVATE && protect == PAGE_READWRITE;
+
+ SegmentKind kind = {state, type, protect, isStack ? 1 : 0};
+ auto entry =
+ static_cast<SegmentEntry*>(table.Add(&kind, mozilla::fallible));
+ if (entry) {
+ entry->mCount += 1;
+ entry->mSize += size;
+ }
+
+ isPrevSegStackGuard = info.State == MEM_COMMIT &&
+ info.Type == MEM_PRIVATE &&
+ info.Protect == (PAGE_READWRITE | PAGE_GUARD);
+
+ size_t lastAddress = currentAddress;
+ currentAddress += size;
+
+ // If we overflow, we've examined all of the address space.
+ if (currentAddress < lastAddress) {
+ break;
+ }
+ }
+
+ // Then iterate over the hash table and report the details for each segment
+ // kind.
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ // For each range of pages, we consider one or more of its State, Type
+ // and Protect values. These are documented at
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx
+ // (for State and Type) and
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx
+ // (for Protect).
+ //
+ // Not all State values have accompanying Type and Protection values.
+ bool doType = false;
+ bool doProtect = false;
+
+ auto entry = static_cast<const SegmentEntry*>(iter.Get());
+
+ nsCString path("address-space");
+
+ switch (entry->mKind.mState) {
+ case MEM_FREE:
+ path.AppendLiteral("/free");
+ break;
+
+ case MEM_RESERVE:
+ path.AppendLiteral("/reserved");
+ doType = true;
+ break;
+
+ case MEM_COMMIT:
+ path.AppendLiteral("/commit");
+ doType = true;
+ doProtect = true;
+ break;
+
+ default:
+ // Should be impossible, but handle it just in case.
+ path.AppendLiteral("/???");
+ break;
+ }
+
+ if (doType) {
+ switch (entry->mKind.mType) {
+ case MEM_IMAGE:
+ path.AppendLiteral("/image");
+ break;
+
+ case MEM_MAPPED:
+ path.AppendLiteral("/mapped");
+ break;
+
+ case MEM_PRIVATE:
+ path.AppendLiteral("/private");
+ break;
+
+ default:
+ // Should be impossible, but handle it just in case.
+ path.AppendLiteral("/???");
+ break;
+ }
+ }
+
+ if (doProtect) {
+ DWORD protect = entry->mKind.mProtect;
+ // Basic attributes. Exactly one of these should be set.
+ if (protect & PAGE_EXECUTE) {
+ path.AppendLiteral("/execute");
+ }
+ if (protect & PAGE_EXECUTE_READ) {
+ path.AppendLiteral("/execute-read");
+ }
+ if (protect & PAGE_EXECUTE_READWRITE) {
+ path.AppendLiteral("/execute-readwrite");
+ }
+ if (protect & PAGE_EXECUTE_WRITECOPY) {
+ path.AppendLiteral("/execute-writecopy");
+ }
+ if (protect & PAGE_NOACCESS) {
+ path.AppendLiteral("/noaccess");
+ }
+ if (protect & PAGE_READONLY) {
+ path.AppendLiteral("/readonly");
+ }
+ if (protect & PAGE_READWRITE) {
+ path.AppendLiteral("/readwrite");
+ }
+ if (protect & PAGE_WRITECOPY) {
+ path.AppendLiteral("/writecopy");
+ }
+
+ // Modifiers. At most one of these should be set.
+ if (protect & PAGE_GUARD) {
+ path.AppendLiteral("+guard");
+ }
+ if (protect & PAGE_NOCACHE) {
+ path.AppendLiteral("+nocache");
+ }
+ if (protect & PAGE_WRITECOMBINE) {
+ path.AppendLiteral("+writecombine");
+ }
+
+ // Annotate likely stack segments, too.
+ if (entry->mKind.mIsStack) {
+ path.AppendLiteral("+stack");
+ }
+ }
+
+ // Append the segment count.
+ path.AppendPrintf("(segments=%u)", entry->mCount);
+
+ aHandleReport->Callback(""_ns, path, KIND_OTHER, UNITS_BYTES,
+ entry->mSize, "From MEMORY_BASIC_INFORMATION."_ns,
+ aData);
+ }
+
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(WindowsAddressSpaceReporter, nsIMemoryReporter)
+
+#endif // XP_<PLATFORM>
+
+#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
+class VsizeMaxContiguousReporter final : public nsIMemoryReporter {
+ ~VsizeMaxContiguousReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount;
+ if (NS_SUCCEEDED(VsizeMaxContiguousDistinguishedAmount(&amount))) {
+ MOZ_COLLECT_REPORT(
+ "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount,
+ "Size of the maximum contiguous block of available virtual memory.");
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter)
+#endif
+
+#ifdef HAVE_PRIVATE_REPORTER
+class PrivateReporter final : public nsIMemoryReporter {
+ ~PrivateReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount;
+ if (NS_SUCCEEDED(PrivateDistinguishedAmount(&amount))) {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "private", KIND_OTHER, UNITS_BYTES, amount,
+"Memory that cannot be shared with other processes, including memory that is "
+"committed and marked MEM_PRIVATE, data that is not mapped, and executable "
+"pages that have been written to.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter)
+#endif
+
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+class VsizeReporter final : public nsIMemoryReporter {
+ ~VsizeReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount;
+ if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "vsize", KIND_OTHER, UNITS_BYTES, amount,
+"Memory mapped by the process, including code and data segments, the heap, "
+"thread stacks, memory explicitly mapped by the process via mmap and similar "
+"operations, and memory shared with other processes. This is the vsize figure "
+"as reported by 'top' and 'ps'. This figure is of limited use on Mac, where "
+"processes share huge amounts of memory with one another. But even on other "
+"operating systems, 'resident' is a much better measure of the memory "
+"resources used by the process.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter)
+
+class ResidentReporter final : public nsIMemoryReporter {
+ ~ResidentReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount;
+ if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "resident", KIND_OTHER, UNITS_BYTES, amount,
+"Memory mapped by the process that is present in physical memory, also known "
+"as the resident set size (RSS). This is the best single figure to use when "
+"considering the memory resources used by the process, but it depends both on "
+"other processes being run and details of the OS kernel and so is best used "
+"for comparing the memory usage of a single process at different points in "
+"time.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter)
+
+#endif // HAVE_VSIZE_AND_RESIDENT_REPORTERS
+
+#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
+class ResidentUniqueReporter final : public nsIMemoryReporter {
+ ~ResidentUniqueReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount = 0;
+ // clang-format off
+ if (NS_SUCCEEDED(ResidentUniqueDistinguishedAmount(&amount))) {
+ MOZ_COLLECT_REPORT(
+ "resident-unique", KIND_OTHER, UNITS_BYTES, amount,
+"Memory mapped by the process that is present in physical memory and not "
+"shared with any other processes. This is also known as the process's unique "
+"set size (USS). This is the amount of RAM we'd expect to be freed if we "
+"closed this process.");
+ }
+#ifdef XP_MACOSX
+ if (NS_SUCCEEDED(PhysicalFootprintAmount(&amount))) {
+ MOZ_COLLECT_REPORT(
+ "resident-phys-footprint", KIND_OTHER, UNITS_BYTES, amount,
+"Memory footprint reported by MacOS's task_info API's phys_footprint field. "
+"This matches the memory column in Activity Monitor.");
+ }
+#endif
+ // clang-format on
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter)
+
+#endif // HAVE_RESIDENT_UNIQUE_REPORTER
+
+#ifdef HAVE_SYSTEM_HEAP_REPORTER
+
+class SystemHeapReporter final : public nsIMemoryReporter {
+ ~SystemHeapReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount;
+ if (NS_SUCCEEDED(SystemHeapSize(&amount))) {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "system-heap-allocated", KIND_OTHER, UNITS_BYTES, amount,
+"Memory used by the system allocator that is currently allocated to the "
+"application. This is distinct from the jemalloc heap that Firefox uses for "
+"most or all of its heap allocations. Ideally this number is zero, but "
+"on some platforms we cannot force every heap allocation through jemalloc.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter)
+#endif // HAVE_SYSTEM_HEAP_REPORTER
+
+#ifdef XP_UNIX
+
+# include <sys/resource.h>
+
+# define HAVE_RESIDENT_PEAK_REPORTER 1
+
+[[nodiscard]] static nsresult ResidentPeakDistinguishedAmount(int64_t* aN) {
+ struct rusage usage;
+ if (0 == getrusage(RUSAGE_SELF, &usage)) {
+ // The units for ru_maxrrs:
+ // - Mac: bytes
+ // - Solaris: pages? But some sources it actually always returns 0, so
+ // check for that
+ // - Linux, {Net/Open/Free}BSD, DragonFly: KiB
+# ifdef XP_MACOSX
+ *aN = usage.ru_maxrss;
+# elif defined(SOLARIS)
+ *aN = usage.ru_maxrss * getpagesize();
+# else
+ *aN = usage.ru_maxrss * 1024;
+# endif
+ if (*aN > 0) {
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+class ResidentPeakReporter final : public nsIMemoryReporter {
+ ~ResidentPeakReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount = 0;
+ if (NS_SUCCEEDED(ResidentPeakDistinguishedAmount(&amount))) {
+ MOZ_COLLECT_REPORT(
+ "resident-peak", KIND_OTHER, UNITS_BYTES, amount,
+ "The peak 'resident' value for the lifetime of the process.");
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(ResidentPeakReporter, nsIMemoryReporter)
+
+# define HAVE_PAGE_FAULT_REPORTERS 1
+
+class PageFaultsSoftReporter final : public nsIMemoryReporter {
+ ~PageFaultsSoftReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ struct rusage usage;
+ int err = getrusage(RUSAGE_SELF, &usage);
+ if (err == 0) {
+ int64_t amount = usage.ru_minflt;
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount,
+"The number of soft page faults (also known as 'minor page faults') that "
+"have occurred since the process started. A soft page fault occurs when the "
+"process tries to access a page which is present in physical memory but is "
+"not mapped into the process's address space. For instance, a process might "
+"observe soft page faults when it loads a shared library which is already "
+"present in physical memory. A process may experience many thousands of soft "
+"page faults even when the machine has plenty of available physical memory, "
+"and because the OS services a soft page fault without accessing the disk, "
+"they impact performance much less than hard page faults.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter)
+
+[[nodiscard]] static nsresult PageFaultsHardDistinguishedAmount(
+ int64_t* aAmount) {
+ struct rusage usage;
+ int err = getrusage(RUSAGE_SELF, &usage);
+ if (err != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ *aAmount = usage.ru_majflt;
+ return NS_OK;
+}
+
+class PageFaultsHardReporter final : public nsIMemoryReporter {
+ ~PageFaultsHardReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ int64_t amount = 0;
+ if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) {
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount,
+"The number of hard page faults (also known as 'major page faults') that have "
+"occurred since the process started. A hard page fault occurs when a process "
+"tries to access a page which is not present in physical memory. The "
+"operating system must access the disk in order to fulfill a hard page fault. "
+"When memory is plentiful, you should see very few hard page faults. But if "
+"the process tries to use more memory than your machine has available, you "
+"may see many thousands of hard page faults. Because accessing the disk is up "
+"to a million times slower than accessing RAM, the program may run very "
+"slowly when it is experiencing more than 100 or so hard page faults a "
+"second.");
+ // clang-format on
+ }
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter)
+
+#endif // XP_UNIX
+
+/**
+ ** memory reporter implementation for jemalloc and OSX malloc,
+ ** to obtain info on total memory in use (that we know about,
+ ** at least -- on OSX, there are sometimes other zones in use).
+ **/
+
+#ifdef HAVE_JEMALLOC_STATS
+
+static size_t HeapOverhead(const jemalloc_stats_t& aStats) {
+ return aStats.waste + aStats.bookkeeping + aStats.page_cache +
+ aStats.bin_unused;
+}
+
+// This has UNITS_PERCENTAGE, so it is multiplied by 100x *again* on top of the
+// 100x for the percentage.
+
+// static
+int64_t nsMemoryReporterManager::HeapOverheadFraction(
+ const jemalloc_stats_t& aStats) {
+ size_t heapOverhead = HeapOverhead(aStats);
+ size_t heapCommitted = aStats.allocated + heapOverhead;
+ return int64_t(10000 * (heapOverhead / (double)heapCommitted));
+}
+
+class JemallocHeapReporter final : public nsIMemoryReporter {
+ ~JemallocHeapReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ jemalloc_stats_t stats;
+ const size_t num_bins = jemalloc_stats_num_bins();
+ nsTArray<jemalloc_bin_stats_t> bin_stats(num_bins);
+ bin_stats.SetLength(num_bins);
+ jemalloc_stats(&stats, bin_stats.Elements());
+
+ // clang-format off
+ MOZ_COLLECT_REPORT(
+ "heap-committed/allocated", KIND_OTHER, UNITS_BYTES, stats.allocated,
+"Memory mapped by the heap allocator that is currently allocated to the "
+"application. This may exceed the amount of memory requested by the "
+"application because the allocator regularly rounds up request sizes. (The "
+"exact amount requested is not recorded.)");
+
+ MOZ_COLLECT_REPORT(
+ "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated,
+"The same as 'heap-committed/allocated'.");
+
+ // We mark this and the other heap-overhead reporters as KIND_NONHEAP
+ // because KIND_HEAP memory means "counted in heap-allocated", which
+ // this is not.
+ for (auto& bin : bin_stats) {
+ MOZ_ASSERT(bin.size);
+ nsPrintfCString path("explicit/heap-overhead/bin-unused/bin-%zu",
+ bin.size);
+ aHandleReport->Callback(EmptyCString(), path, KIND_NONHEAP, UNITS_BYTES,
+ bin.bytes_unused,
+ nsLiteralCString(
+ "Unused bytes in all runs of all bins for this size class"),
+ aData);
+ }
+
+ if (stats.waste > 0) {
+ MOZ_COLLECT_REPORT(
+ "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES,
+ stats.waste,
+"Committed bytes which do not correspond to an active allocation and which the "
+"allocator is not intentionally keeping alive (i.e., not "
+"'explicit/heap-overhead/{bookkeeping,page-cache,bin-unused}').");
+ }
+
+ MOZ_COLLECT_REPORT(
+ "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES,
+ stats.bookkeeping,
+"Committed bytes which the heap allocator uses for internal data structures.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES,
+ stats.page_cache,
+"Memory which the allocator could return to the operating system, but hasn't. "
+"The allocator keeps this memory around as an optimization, so it doesn't "
+"have to ask the OS the next time it needs to fulfill a request. This value "
+"is typically not larger than a few megabytes.");
+
+ MOZ_COLLECT_REPORT(
+ "heap-committed/overhead", KIND_OTHER, UNITS_BYTES,
+ HeapOverhead(stats),
+"The sum of 'explicit/heap-overhead/*'.");
+
+ MOZ_COLLECT_REPORT(
+ "heap-mapped", KIND_OTHER, UNITS_BYTES, stats.mapped,
+"Amount of memory currently mapped. Includes memory that is uncommitted, i.e. "
+"neither in physical memory nor paged to disk.");
+
+ MOZ_COLLECT_REPORT(
+ "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize,
+ "Size of chunks.");
+
+#ifdef MOZ_PHC
+ mozilla::phc::MemoryUsage usage;
+ mozilla::phc::PHCMemoryUsage(usage);
+
+ MOZ_COLLECT_REPORT(
+ "explicit/heap-overhead/phc/metadata", KIND_NONHEAP, UNITS_BYTES,
+ usage.mMetadataBytes,
+"Memory used by PHC to store stacks and other metadata for each allocation");
+ MOZ_COLLECT_REPORT(
+ "explicit/heap-overhead/phc/fragmentation", KIND_NONHEAP, UNITS_BYTES,
+ usage.mFragmentationBytes,
+"The amount of memory lost due to rounding up allocations to the next page "
+"size. "
+"This is also known as 'internal fragmentation'. "
+"Note that all allocators have some internal fragmentation, there may still "
+"be some internal fragmentation without PHC.");
+#endif
+
+ // clang-format on
+
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter)
+
+#endif // HAVE_JEMALLOC_STATS
+
+// Why is this here? At first glance, you'd think it could be defined and
+// registered with nsMemoryReporterManager entirely within nsAtomTable.cpp.
+// However, the obvious time to register it is when the table is initialized,
+// and that happens before XPCOM components are initialized, which means the
+// RegisterStrongMemoryReporter call fails. So instead we do it here.
+class AtomTablesReporter final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~AtomTablesReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ AtomsSizes sizes;
+ NS_AddSizeOfAtoms(MallocSizeOf, sizes);
+
+ MOZ_COLLECT_REPORT("explicit/atoms/table", KIND_HEAP, UNITS_BYTES,
+ sizes.mTable, "Memory used by the atom table.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/atoms/dynamic-objects-and-chars", KIND_HEAP, UNITS_BYTES,
+ sizes.mDynamicAtoms,
+ "Memory used by dynamic atom objects and chars (which are stored "
+ "at the end of each atom object).");
+
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter)
+
+class ThreadsReporter final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+ ~ThreadsReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+#ifdef XP_LINUX
+ nsTArray<MemoryMapping> mappings(1024);
+ MOZ_TRY(GetMemoryMappings(mappings));
+#endif
+
+ // Enumerating over active threads requires holding a lock, so we collect
+ // info on all threads, and then call our reporter callbacks after releasing
+ // the lock.
+ struct ThreadData {
+ nsCString mName;
+ uint32_t mThreadId;
+ size_t mPrivateSize;
+ };
+ AutoTArray<ThreadData, 32> threads;
+
+ size_t eventQueueSizes = 0;
+ size_t wrapperSizes = 0;
+ size_t threadCount = 0;
+
+ {
+ nsThreadManager& tm = nsThreadManager::get();
+ OffTheBooksMutexAutoLock lock(tm.ThreadListMutex());
+ for (auto* thread : tm.ThreadList()) {
+ threadCount++;
+ eventQueueSizes += thread->SizeOfEventQueues(MallocSizeOf);
+ wrapperSizes += thread->ShallowSizeOfIncludingThis(MallocSizeOf);
+
+ if (!thread->StackBase()) {
+ continue;
+ }
+
+#if defined(XP_LINUX)
+ int idx = mappings.BinaryIndexOf(thread->StackBase());
+ if (idx < 0) {
+ continue;
+ }
+ // Referenced() is the combined size of all pages in the region which
+ // have ever been touched, and are therefore consuming memory. For stack
+ // regions, these pages are guaranteed to be un-shared unless we fork
+ // after creating threads (which we don't).
+ size_t privateSize = mappings[idx].Referenced();
+
+ // On Linux, we have to be very careful matching memory regions to
+ // thread stacks.
+ //
+ // To begin with, the kernel only reports VM stats for regions of all
+ // adjacent pages with the same flags, protection, and backing file.
+ // There's no way to get finer-grained usage information for a subset of
+ // those pages.
+ //
+ // Stack segments always have a guard page at the bottom of the stack
+ // (assuming we only support stacks that grow down), so there's no
+ // danger of them being merged with other stack regions. At the top,
+ // there's no protection page, and no way to allocate one without using
+ // pthreads directly and allocating our own stacks. So we get around the
+ // problem by adding an extra VM flag (NOHUGEPAGES) to our stack region,
+ // which we don't expect to be set on any heap regions. But this is not
+ // fool-proof.
+ //
+ // A second kink is that different C libraries (and different versions
+ // thereof) report stack base locations and sizes differently with
+ // regard to the guard page. For the libraries that include the guard
+ // page in the stack size base pointer, we need to adjust those values
+ // to compensate. But it's possible that our logic will get out of sync
+ // with library changes, or someone will compile with an unexpected
+ // library.
+ //
+ //
+ // The upshot of all of this is that there may be configurations that
+ // our special cases don't cover. And if there are, we want to know
+ // about it. So assert that total size of the memory region we're
+ // reporting actually matches the allocated size of the thread stack.
+# ifndef ANDROID
+ MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(),
+ "Mapping region size doesn't match stack allocation size");
+# endif
+#elif defined(XP_WIN)
+ auto memInfo =
+ MemoryInfo::Get(thread->StackBase(), thread->StackSize());
+ size_t privateSize = memInfo.Committed();
+#else
+ size_t privateSize = thread->StackSize();
+ MOZ_ASSERT_UNREACHABLE(
+ "Shouldn't have stack base pointer on this "
+ "platform");
+#endif
+
+ nsCString threadName;
+ thread->GetThreadName(threadName);
+ threads.AppendElement(ThreadData{
+ std::move(threadName),
+ thread->ThreadId(),
+ // On Linux, it's possible (but unlikely) that our stack region will
+ // have been merged with adjacent heap regions, in which case we'll
+ // get combined size information for both. So we take the minimum of
+ // the reported private size and the requested stack size to avoid
+ // the possible of majorly over-reporting in that case.
+ std::min(privateSize, thread->StackSize()),
+ });
+ }
+ }
+
+ for (auto& thread : threads) {
+ nsPrintfCString path("explicit/threads/stacks/%s (tid=%u)",
+ thread.mName.get(), thread.mThreadId);
+
+ aHandleReport->Callback(
+ ""_ns, path, KIND_NONHEAP, UNITS_BYTES, thread.mPrivateSize,
+ nsLiteralCString("The sizes of thread stacks which have been "
+ "committed to memory."),
+ aData);
+ }
+
+ MOZ_COLLECT_REPORT("explicit/threads/overhead/event-queues", KIND_HEAP,
+ UNITS_BYTES, eventQueueSizes,
+ "The sizes of nsThread event queues and observers.");
+
+ MOZ_COLLECT_REPORT("explicit/threads/overhead/wrappers", KIND_HEAP,
+ UNITS_BYTES, wrapperSizes,
+ "The sizes of nsThread/PRThread wrappers.");
+
+#if defined(XP_WIN)
+ // Each thread on Windows has a fixed kernel overhead. For 32 bit Windows,
+ // that's 12K. For 64 bit, it's 24K.
+ //
+ // See
+ // https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/
+ constexpr size_t kKernelSize = (sizeof(void*) == 8 ? 24 : 12) * 1024;
+#elif defined(XP_LINUX)
+ // On Linux, kernel stacks are usually 8K. However, on x86, they are
+ // allocated virtually, and start out at 4K. They may grow to 8K, but we
+ // have no way of knowing which ones do, so all we can do is guess.
+# if defined(__x86_64__) || defined(__i386__)
+ constexpr size_t kKernelSize = 4 * 1024;
+# else
+ constexpr size_t kKernelSize = 8 * 1024;
+# endif
+#elif defined(XP_MACOSX)
+ // On Darwin, kernel stacks are 16K:
+ //
+ // https://books.google.com/books?id=K8vUkpOXhN4C&lpg=PA513&dq=mach%20kernel%20thread%20stack%20size&pg=PA513#v=onepage&q=mach%20kernel%20thread%20stack%20size&f=false
+ constexpr size_t kKernelSize = 16 * 1024;
+#else
+ // Elsewhere, just assume that kernel stacks require at least 8K.
+ constexpr size_t kKernelSize = 8 * 1024;
+#endif
+
+ MOZ_COLLECT_REPORT("explicit/threads/overhead/kernel", KIND_NONHEAP,
+ UNITS_BYTES, threadCount * kKernelSize,
+ "The total kernel overhead for all active threads.");
+
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(ThreadsReporter, nsIMemoryReporter)
+
+#ifdef DEBUG
+
+// Ideally, this would be implemented in BlockingResourceBase.cpp.
+// However, this ends up breaking the linking step of various unit tests due
+// to adding a new dependency to libdmd for a commonly used feature (mutexes)
+// in DMD builds. So instead we do it here.
+class DeadlockDetectorReporter final : public nsIMemoryReporter {
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~DeadlockDetectorReporter() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ MOZ_COLLECT_REPORT(
+ "explicit/deadlock-detector", KIND_HEAP, UNITS_BYTES,
+ BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf),
+ "Memory used by the deadlock detector.");
+
+ return NS_OK;
+ }
+};
+NS_IMPL_ISUPPORTS(DeadlockDetectorReporter, nsIMemoryReporter)
+
+#endif
+
+#ifdef MOZ_DMD
+
+namespace mozilla {
+namespace dmd {
+
+class DMDReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override {
+ dmd::Sizes sizes;
+ dmd::SizeOf(&sizes);
+
+ MOZ_COLLECT_REPORT(
+ "explicit/dmd/stack-traces/used", KIND_HEAP, UNITS_BYTES,
+ sizes.mStackTracesUsed,
+ "Memory used by stack traces which correspond to at least "
+ "one heap block DMD is tracking.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/dmd/stack-traces/unused", KIND_HEAP, UNITS_BYTES,
+ sizes.mStackTracesUnused,
+ "Memory used by stack traces which don't correspond to any heap "
+ "blocks DMD is currently tracking.");
+
+ MOZ_COLLECT_REPORT("explicit/dmd/stack-traces/table", KIND_HEAP,
+ UNITS_BYTES, sizes.mStackTraceTable,
+ "Memory used by DMD's stack trace table.");
+
+ MOZ_COLLECT_REPORT("explicit/dmd/live-block-table", KIND_HEAP, UNITS_BYTES,
+ sizes.mLiveBlockTable,
+ "Memory used by DMD's live block table.");
+
+ MOZ_COLLECT_REPORT("explicit/dmd/dead-block-list", KIND_HEAP, UNITS_BYTES,
+ sizes.mDeadBlockTable,
+ "Memory used by DMD's dead block list.");
+
+ return NS_OK;
+ }
+
+ private:
+ ~DMDReporter() = default;
+};
+NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter)
+
+} // namespace dmd
+} // namespace mozilla
+
+#endif // MOZ_DMD
+
+#ifdef MOZ_WIDGET_ANDROID
+class AndroidMemoryReporter final : public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+
+ AndroidMemoryReporter() = default;
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override {
+ if (!jni::IsAvailable() || jni::GetAPIVersion() < 23) {
+ return NS_OK;
+ }
+
+ int32_t heap = java::GeckoAppShell::GetMemoryUsage("summary.java-heap"_ns);
+ if (heap > 0) {
+ MOZ_COLLECT_REPORT("java-heap", KIND_OTHER, UNITS_BYTES, heap * 1024,
+ "The private Java Heap usage");
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~AndroidMemoryReporter() = default;
+};
+
+NS_IMPL_ISUPPORTS(AndroidMemoryReporter, nsIMemoryReporter)
+#endif
+
+/**
+ ** nsMemoryReporterManager implementation
+ **/
+
+NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager,
+ nsIMemoryReporter)
+
+NS_IMETHODIMP
+nsMemoryReporterManager::Init() {
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ // Under normal circumstances this function is only called once. However,
+ // we've (infrequently) seen memory report dumps in crash reports that
+ // suggest that this function is sometimes called multiple times. That in
+ // turn means that multiple reporters of each kind are registered, which
+ // leads to duplicated reports of individual measurements such as "resident",
+ // "vsize", etc.
+ //
+ // It's unclear how these multiple calls can occur. The only plausible theory
+ // so far is badly-written extensions, because this function is callable from
+ // JS code via nsIMemoryReporter.idl.
+ //
+ // Whatever the cause, it's a bad thing. So we protect against it with the
+ // following check.
+ static bool isInited = false;
+ if (isInited) {
+ NS_WARNING("nsMemoryReporterManager::Init() has already been called!");
+ return NS_OK;
+ }
+ isInited = true;
+
+#ifdef HAVE_JEMALLOC_STATS
+ RegisterStrongReporter(new JemallocHeapReporter());
+#endif
+
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+ RegisterStrongReporter(new VsizeReporter());
+ RegisterStrongReporter(new ResidentReporter());
+#endif
+
+#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
+ RegisterStrongReporter(new VsizeMaxContiguousReporter());
+#endif
+
+#ifdef HAVE_RESIDENT_PEAK_REPORTER
+ RegisterStrongReporter(new ResidentPeakReporter());
+#endif
+
+#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
+ RegisterStrongReporter(new ResidentUniqueReporter());
+#endif
+
+#ifdef HAVE_PAGE_FAULT_REPORTERS
+ RegisterStrongReporter(new PageFaultsSoftReporter());
+ RegisterStrongReporter(new PageFaultsHardReporter());
+#endif
+
+#ifdef HAVE_PRIVATE_REPORTER
+ RegisterStrongReporter(new PrivateReporter());
+#endif
+
+#ifdef HAVE_SYSTEM_HEAP_REPORTER
+ RegisterStrongReporter(new SystemHeapReporter());
+#endif
+
+ RegisterStrongReporter(new AtomTablesReporter());
+
+ RegisterStrongReporter(new ThreadsReporter());
+
+#ifdef DEBUG
+ RegisterStrongReporter(new DeadlockDetectorReporter());
+#endif
+
+#ifdef MOZ_GECKO_PROFILER
+ // We have to register this here rather than in profiler_init() because
+ // profiler_init() runs prior to nsMemoryReporterManager's creation.
+ RegisterStrongReporter(new GeckoProfilerReporter());
+#endif
+
+#ifdef MOZ_DMD
+ RegisterStrongReporter(new mozilla::dmd::DMDReporter());
+#endif
+
+#ifdef XP_WIN
+ RegisterStrongReporter(new WindowsAddressSpaceReporter());
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ RegisterStrongReporter(new AndroidMemoryReporter());
+#endif
+
+#ifdef XP_UNIX
+ nsMemoryInfoDumper::Initialize();
+#endif
+
+ // Report our own memory usage as well.
+ RegisterWeakReporter(this);
+
+ return NS_OK;
+}
+
+nsMemoryReporterManager::nsMemoryReporterManager()
+ : mMutex("nsMemoryReporterManager::mMutex"),
+ mIsRegistrationBlocked(false),
+ mStrongReporters(new StrongReportersTable()),
+ mWeakReporters(new WeakReportersTable()),
+ mSavedStrongReporters(nullptr),
+ mSavedWeakReporters(nullptr),
+ mNextGeneration(1),
+ mPendingProcessesState(nullptr),
+ mPendingReportersState(nullptr)
+#ifdef HAVE_JEMALLOC_STATS
+ ,
+ mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID))
+#endif
+{
+}
+
+nsMemoryReporterManager::~nsMemoryReporterManager() {
+ delete mStrongReporters;
+ delete mWeakReporters;
+ NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters");
+ NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters");
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ size_t n = MallocSizeOf(this);
+ {
+ mozilla::MutexAutoLock autoLock(mMutex);
+ n += mStrongReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
+ n += mWeakReporters->ShallowSizeOfIncludingThis(MallocSizeOf);
+ }
+
+ MOZ_COLLECT_REPORT("explicit/memory-reporter-manager", KIND_HEAP, UNITS_BYTES,
+ n, "Memory used by the memory reporter infrastructure.");
+
+ return NS_OK;
+}
+
+#ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING
+# define MEMORY_REPORTING_LOG(format, ...) \
+ printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__);
+#else
+# define MEMORY_REPORTING_LOG(...)
+#endif
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
+ nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData, bool aAnonymize) {
+ return GetReportsExtended(aHandleReport, aHandleReportData, aFinishReporting,
+ aFinishReportingData, aAnonymize,
+ /* minimize = */ false,
+ /* DMDident = */ u""_ns);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetReportsExtended(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
+ nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData, bool aAnonymize, bool aMinimize,
+ const nsAString& aDMDDumpIdent) {
+ nsresult rv;
+
+ // Memory reporters are not necessarily threadsafe, so this function must
+ // be called from the main thread.
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ uint32_t generation = mNextGeneration++;
+
+ if (mPendingProcessesState) {
+ // A request is in flight. Don't start another one. And don't report
+ // an error; just ignore it, and let the in-flight request finish.
+ MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", generation,
+ mPendingProcessesState->mGeneration);
+ return NS_OK;
+ }
+
+ MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation);
+
+ uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1);
+ MOZ_ASSERT(concurrency >= 1);
+ if (concurrency < 1) {
+ concurrency = 1;
+ }
+ mPendingProcessesState = new PendingProcessesState(
+ generation, aAnonymize, aMinimize, concurrency, aHandleReport,
+ aHandleReportData, aFinishReporting, aFinishReportingData, aDMDDumpIdent);
+
+ if (aMinimize) {
+ nsCOMPtr<nsIRunnable> callback =
+ NewRunnableMethod("nsMemoryReporterManager::StartGettingReports", this,
+ &nsMemoryReporterManager::StartGettingReports);
+ rv = MinimizeMemoryUsage(callback);
+ } else {
+ rv = StartGettingReports();
+ }
+ return rv;
+}
+
+// MainThread only
+nsresult nsMemoryReporterManager::StartGettingReports() {
+ PendingProcessesState* s = mPendingProcessesState;
+ nsresult rv;
+
+ // Get reports for this process.
+ FILE* parentDMDFile = nullptr;
+#ifdef MOZ_DMD
+ if (!s->mDMDDumpIdent.IsEmpty()) {
+ rv = nsMemoryInfoDumper::OpenDMDFile(s->mDMDDumpIdent, getpid(),
+ &parentDMDFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Proceed with the memory report as if DMD were disabled.
+ parentDMDFile = nullptr;
+ }
+ }
+#endif
+
+ // This is async.
+ GetReportsForThisProcessExtended(
+ s->mHandleReport, s->mHandleReportData, s->mAnonymize, parentDMDFile,
+ s->mFinishReporting, s->mFinishReportingData);
+
+ nsTArray<dom::ContentParent*> childWeakRefs;
+ dom::ContentParent::GetAll(childWeakRefs);
+ if (!childWeakRefs.IsEmpty()) {
+ // Request memory reports from child processes. This happens
+ // after the parent report so that the parent's main thread will
+ // be free to process the child reports, instead of causing them
+ // to be buffered and consume (possibly scarce) memory.
+
+ for (size_t i = 0; i < childWeakRefs.Length(); ++i) {
+ s->mChildrenPending.AppendElement(childWeakRefs[i]);
+ }
+ }
+
+ if (gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get()) {
+ if (RefPtr<MemoryReportingProcess> proc = gpu->GetProcessMemoryReporter()) {
+ s->mChildrenPending.AppendElement(proc.forget());
+ }
+ }
+
+ if (RDDProcessManager* rdd = RDDProcessManager::Get()) {
+ if (RefPtr<MemoryReportingProcess> proc = rdd->GetProcessMemoryReporter()) {
+ s->mChildrenPending.AppendElement(proc.forget());
+ }
+ }
+
+ if (gfx::VRProcessManager* vr = gfx::VRProcessManager::Get()) {
+ if (RefPtr<MemoryReportingProcess> proc = vr->GetProcessMemoryReporter()) {
+ s->mChildrenPending.AppendElement(proc.forget());
+ }
+ }
+
+ if (!IsRegistrationBlocked() && net::gIOService) {
+ if (RefPtr<MemoryReportingProcess> proc =
+ net::gIOService->GetSocketProcessMemoryReporter()) {
+ s->mChildrenPending.AppendElement(proc.forget());
+ }
+ }
+
+ if (!IsRegistrationBlocked()) {
+ if (RefPtr<UtilityProcessManager> utility =
+ UtilityProcessManager::GetIfExists()) {
+ for (RefPtr<UtilityProcessParent>& parent :
+ utility->GetAllProcessesProcessParent()) {
+ if (RefPtr<MemoryReportingProcess> proc =
+ utility->GetProcessMemoryReporter(parent)) {
+ s->mChildrenPending.AppendElement(proc.forget());
+ }
+ }
+ }
+ }
+
+ if (!s->mChildrenPending.IsEmpty()) {
+ nsCOMPtr<nsITimer> timer;
+ rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer), TimeoutCallback, this, kTimeoutLengthMS,
+ nsITimer::TYPE_ONE_SHOT,
+ "nsMemoryReporterManager::StartGettingReports");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FinishReporting();
+ return rv;
+ }
+
+ MOZ_ASSERT(!s->mTimer);
+ s->mTimer.swap(timer);
+ }
+
+ return NS_OK;
+}
+
+void nsMemoryReporterManager::DispatchReporter(
+ nsIMemoryReporter* aReporter, bool aIsAsync,
+ nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
+ bool aAnonymize) {
+ MOZ_ASSERT(mPendingReportersState);
+
+ // Grab refs to everything used in the lambda function.
+ RefPtr<nsMemoryReporterManager> self = this;
+ nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
+ nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
+ nsCOMPtr<nsISupports> handleReportData = aHandleReportData;
+
+ nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction(
+ "nsMemoryReporterManager::DispatchReporter",
+ [self, reporter, aIsAsync, handleReport, handleReportData, aAnonymize]() {
+ reporter->CollectReports(handleReport, handleReportData, aAnonymize);
+ if (!aIsAsync) {
+ self->EndReport();
+ }
+ });
+
+ NS_DispatchToMainThread(event);
+ mPendingReportersState->mReportsPending++;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetReportsForThisProcessExtended(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData,
+ bool aAnonymize, FILE* aDMDFile,
+ nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData) {
+ // Memory reporters are not necessarily threadsafe, so this function must
+ // be called from the main thread.
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ if (NS_WARN_IF(mPendingReportersState)) {
+ // Report is already in progress.
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+#ifdef MOZ_DMD
+ if (aDMDFile) {
+ // Clear DMD's reportedness state before running the memory
+ // reporters, to avoid spurious twice-reported warnings.
+ dmd::ClearReports();
+ }
+#else
+ MOZ_ASSERT(!aDMDFile);
+#endif
+
+ mPendingReportersState = new PendingReportersState(
+ aFinishReporting, aFinishReportingData, aDMDFile);
+
+ {
+ mozilla::MutexAutoLock autoLock(mMutex);
+
+ for (const auto& entry : *mStrongReporters) {
+ DispatchReporter(entry.GetKey(), entry.GetData(), aHandleReport,
+ aHandleReportData, aAnonymize);
+ }
+
+ for (const auto& entry : *mWeakReporters) {
+ nsCOMPtr<nsIMemoryReporter> reporter = entry.GetKey();
+ DispatchReporter(reporter, entry.GetData(), aHandleReport,
+ aHandleReportData, aAnonymize);
+ }
+ }
+
+ return NS_OK;
+}
+
+// MainThread only
+NS_IMETHODIMP
+nsMemoryReporterManager::EndReport() {
+ if (--mPendingReportersState->mReportsPending == 0) {
+#ifdef MOZ_DMD
+ if (mPendingReportersState->mDMDFile) {
+ nsMemoryInfoDumper::DumpDMDToFile(mPendingReportersState->mDMDFile);
+ }
+#endif
+ if (mPendingProcessesState) {
+ // This is the parent process.
+ EndProcessReport(mPendingProcessesState->mGeneration, true);
+ } else {
+ mPendingReportersState->mFinishReporting->Callback(
+ mPendingReportersState->mFinishReportingData);
+ }
+
+ delete mPendingReportersState;
+ mPendingReportersState = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsMemoryReporterManager::PendingProcessesState*
+nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration) {
+ // Memory reporting only happens on the main thread.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ PendingProcessesState* s = mPendingProcessesState;
+
+ if (!s) {
+ // If we reach here, then:
+ //
+ // - A child process reported back too late, and no subsequent request
+ // is in flight.
+ //
+ // So there's nothing to be done. Just ignore it.
+ MEMORY_REPORTING_LOG("HandleChildReports: no request in flight (aGen=%u)\n",
+ aGeneration);
+ return nullptr;
+ }
+
+ if (aGeneration != s->mGeneration) {
+ // If we reach here, a child process must have reported back, too late,
+ // while a subsequent (higher-numbered) request is in flight. Again,
+ // ignore it.
+ MOZ_ASSERT(aGeneration < s->mGeneration);
+ MEMORY_REPORTING_LOG(
+ "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", aGeneration,
+ s->mGeneration);
+ return nullptr;
+ }
+
+ return s;
+}
+
+// This function has no return value. If something goes wrong, there's no
+// clear place to report the problem to, but that's ok -- we will end up
+// hitting the timeout and executing TimeoutCallback().
+void nsMemoryReporterManager::HandleChildReport(
+ uint32_t aGeneration, const dom::MemoryReport& aChildReport) {
+ PendingProcessesState* s = GetStateForGeneration(aGeneration);
+ if (!s) {
+ return;
+ }
+
+ // Child reports should have a non-empty process.
+ MOZ_ASSERT(!aChildReport.process().IsEmpty());
+
+ // If the call fails, ignore and continue.
+ s->mHandleReport->Callback(aChildReport.process(), aChildReport.path(),
+ aChildReport.kind(), aChildReport.units(),
+ aChildReport.amount(), aChildReport.desc(),
+ s->mHandleReportData);
+}
+
+/* static */
+bool nsMemoryReporterManager::StartChildReport(
+ mozilla::MemoryReportingProcess* aChild,
+ const PendingProcessesState* aState) {
+ if (!aChild->IsAlive()) {
+ MEMORY_REPORTING_LOG(
+ "StartChildReports (gen=%u): child exited before"
+ " its report was started\n",
+ aState->mGeneration);
+ return false;
+ }
+
+ Maybe<mozilla::ipc::FileDescriptor> dmdFileDesc;
+#ifdef MOZ_DMD
+ if (!aState->mDMDDumpIdent.IsEmpty()) {
+ FILE* dmdFile = nullptr;
+ nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent,
+ aChild->Pid(), &dmdFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Proceed with the memory report as if DMD were disabled.
+ dmdFile = nullptr;
+ }
+ if (dmdFile) {
+ dmdFileDesc = Some(mozilla::ipc::FILEToFileDescriptor(dmdFile));
+ fclose(dmdFile);
+ }
+ }
+#endif
+ return aChild->SendRequestMemoryReport(
+ aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc);
+}
+
+void nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration,
+ bool aSuccess) {
+ PendingProcessesState* s = GetStateForGeneration(aGeneration);
+ if (!s) {
+ return;
+ }
+
+ MOZ_ASSERT(s->mNumProcessesRunning > 0);
+ s->mNumProcessesRunning--;
+ s->mNumProcessesCompleted++;
+ MEMORY_REPORTING_LOG(
+ "HandleChildReports (aGen=%u): process %u %s"
+ " (%u running, %u pending)\n",
+ aGeneration, s->mNumProcessesCompleted,
+ aSuccess ? "completed" : "exited during report", s->mNumProcessesRunning,
+ static_cast<unsigned>(s->mChildrenPending.Length()));
+
+ // Start pending children up to the concurrency limit.
+ while (s->mNumProcessesRunning < s->mConcurrencyLimit &&
+ !s->mChildrenPending.IsEmpty()) {
+ // Pop last element from s->mChildrenPending
+ const RefPtr<MemoryReportingProcess> nextChild =
+ s->mChildrenPending.PopLastElement();
+ // Start report (if the child is still alive).
+ if (StartChildReport(nextChild, s)) {
+ ++s->mNumProcessesRunning;
+ MEMORY_REPORTING_LOG(
+ "HandleChildReports (aGen=%u): started child report"
+ " (%u running, %u pending)\n",
+ aGeneration, s->mNumProcessesRunning,
+ static_cast<unsigned>(s->mChildrenPending.Length()));
+ }
+ }
+
+ // If all the child processes (if any) have reported, we can cancel
+ // the timer (if started) and finish up. Otherwise, just return.
+ if (s->mNumProcessesRunning == 0) {
+ MOZ_ASSERT(s->mChildrenPending.IsEmpty());
+ if (s->mTimer) {
+ s->mTimer->Cancel();
+ }
+ FinishReporting();
+ }
+}
+
+/* static */
+void nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) {
+ nsMemoryReporterManager* mgr = static_cast<nsMemoryReporterManager*>(aData);
+ PendingProcessesState* s = mgr->mPendingProcessesState;
+
+ // Release assert because: if the pointer is null we're about to
+ // crash regardless of DEBUG, and this way the compiler doesn't
+ // complain about unused variables.
+ MOZ_RELEASE_ASSERT(s, "mgr->mPendingProcessesState");
+ MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n",
+ s->mGeneration, s->mNumProcessesRunning,
+ static_cast<unsigned>(s->mChildrenPending.Length()));
+
+ // We don't bother sending any kind of cancellation message to the child
+ // processes that haven't reported back.
+ mgr->FinishReporting();
+}
+
+nsresult nsMemoryReporterManager::FinishReporting() {
+ // Memory reporting only happens on the main thread.
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH();
+ }
+
+ MOZ_ASSERT(mPendingProcessesState);
+ MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n",
+ mPendingProcessesState->mGeneration,
+ mPendingProcessesState->mNumProcessesCompleted);
+
+ // Call this before deleting |mPendingProcessesState|. That way, if
+ // |mFinishReportData| calls GetReports(), it will silently abort, as
+ // required.
+ nsresult rv = mPendingProcessesState->mFinishReporting->Callback(
+ mPendingProcessesState->mFinishReportingData);
+
+ delete mPendingProcessesState;
+ mPendingProcessesState = nullptr;
+ return rv;
+}
+
+nsMemoryReporterManager::PendingProcessesState::PendingProcessesState(
+ uint32_t aGeneration, bool aAnonymize, bool aMinimize,
+ uint32_t aConcurrencyLimit, nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandleReportData,
+ nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData, const nsAString& aDMDDumpIdent)
+ : mGeneration(aGeneration),
+ mAnonymize(aAnonymize),
+ mMinimize(aMinimize),
+ mNumProcessesRunning(1), // reporting starts with the parent
+ mNumProcessesCompleted(0),
+ mConcurrencyLimit(aConcurrencyLimit),
+ mHandleReport(aHandleReport),
+ mHandleReportData(aHandleReportData),
+ mFinishReporting(aFinishReporting),
+ mFinishReportingData(aFinishReportingData),
+ mDMDDumpIdent(aDMDDumpIdent) {}
+
+static void CrashIfRefcountIsZero(nsISupports* aObj) {
+ // This will probably crash if the object's refcount is 0.
+ uint32_t refcnt = NS_ADDREF(aObj);
+ if (refcnt <= 1) {
+ MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero");
+ }
+ NS_RELEASE(aObj);
+}
+
+nsresult nsMemoryReporterManager::RegisterReporterHelper(
+ nsIMemoryReporter* aReporter, bool aForce, bool aStrong, bool aIsAsync) {
+ // This method is thread-safe.
+ mozilla::MutexAutoLock autoLock(mMutex);
+
+ if (mIsRegistrationBlocked && !aForce) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mStrongReporters->Contains(aReporter) ||
+ mWeakReporters->Contains(aReporter)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take
+ // a kung fu death grip before calling PutEntry. Otherwise, if PutEntry
+ // addref'ed and released |aReporter| before finally addref'ing it for
+ // good, it would free aReporter! The kung fu death grip could itself be
+ // problematic if PutEntry didn't addref |aReporter| (because then when the
+ // death grip goes out of scope, we would delete the reporter). In debug
+ // mode, we check that this doesn't happen.
+ //
+ // If |aStrong| is false, we require that |aReporter| have a non-zero
+ // refcnt.
+ //
+ if (aStrong) {
+ nsCOMPtr<nsIMemoryReporter> kungFuDeathGrip = aReporter;
+ mStrongReporters->InsertOrUpdate(aReporter, aIsAsync);
+ CrashIfRefcountIsZero(aReporter);
+ } else {
+ CrashIfRefcountIsZero(aReporter);
+ nsCOMPtr<nsIXPConnectWrappedJS> jsComponent = do_QueryInterface(aReporter);
+ if (jsComponent) {
+ // We cannot allow non-native reporters (WrappedJS), since we'll be
+ // holding onto a raw pointer, which would point to the wrapper,
+ // and that wrapper is likely to go away as soon as this register
+ // call finishes. This would then lead to subsequent crashes in
+ // CollectReports().
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+ mWeakReporters->InsertOrUpdate(aReporter, aIsAsync);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) {
+ return RegisterReporterHelper(aReporter, /* force = */ false,
+ /* strong = */ true,
+ /* async = */ false);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterStrongAsyncReporter(
+ nsIMemoryReporter* aReporter) {
+ return RegisterReporterHelper(aReporter, /* force = */ false,
+ /* strong = */ true,
+ /* async = */ true);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) {
+ return RegisterReporterHelper(aReporter, /* force = */ false,
+ /* strong = */ false,
+ /* async = */ false);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterWeakAsyncReporter(
+ nsIMemoryReporter* aReporter) {
+ return RegisterReporterHelper(aReporter, /* force = */ false,
+ /* strong = */ false,
+ /* async = */ true);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked(
+ nsIMemoryReporter* aReporter) {
+ return RegisterReporterHelper(aReporter, /* force = */ true,
+ /* strong = */ true,
+ /* async = */ false);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::UnregisterStrongReporter(
+ nsIMemoryReporter* aReporter) {
+ // This method is thread-safe.
+ mozilla::MutexAutoLock autoLock(mMutex);
+
+ MOZ_ASSERT(!mWeakReporters->Contains(aReporter));
+
+ if (mStrongReporters->Contains(aReporter)) {
+ mStrongReporters->Remove(aReporter);
+ return NS_OK;
+ }
+
+ // We don't register new reporters when the block is in place, but we do
+ // unregister existing reporters. This is so we don't keep holding strong
+ // references that these reporters aren't expecting (which can keep them
+ // alive longer than intended).
+ if (mSavedStrongReporters && mSavedStrongReporters->Contains(aReporter)) {
+ mSavedStrongReporters->Remove(aReporter);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) {
+ // This method is thread-safe.
+ mozilla::MutexAutoLock autoLock(mMutex);
+
+ MOZ_ASSERT(!mStrongReporters->Contains(aReporter));
+
+ if (mWeakReporters->Contains(aReporter)) {
+ mWeakReporters->Remove(aReporter);
+ return NS_OK;
+ }
+
+ // We don't register new reporters when the block is in place, but we do
+ // unregister existing reporters. This is so we don't keep holding weak
+ // references that the old reporters aren't expecting (which can end up as
+ // dangling pointers that lead to use-after-frees).
+ if (mSavedWeakReporters && mSavedWeakReporters->Contains(aReporter)) {
+ mSavedWeakReporters->Remove(aReporter);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() {
+ // This method is thread-safe.
+ mozilla::MutexAutoLock autoLock(mMutex);
+ if (mIsRegistrationBlocked) {
+ return NS_ERROR_FAILURE;
+ }
+ mIsRegistrationBlocked = true;
+
+ // Hide the existing reporters, saving them for later restoration.
+ MOZ_ASSERT(!mSavedStrongReporters);
+ MOZ_ASSERT(!mSavedWeakReporters);
+ mSavedStrongReporters = mStrongReporters;
+ mSavedWeakReporters = mWeakReporters;
+ mStrongReporters = new StrongReportersTable();
+ mWeakReporters = new WeakReportersTable();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() {
+ // This method is thread-safe.
+ mozilla::MutexAutoLock autoLock(mMutex);
+ if (!mIsRegistrationBlocked) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Banish the current reporters, and restore the hidden ones.
+ delete mStrongReporters;
+ delete mWeakReporters;
+ mStrongReporters = mSavedStrongReporters;
+ mWeakReporters = mSavedWeakReporters;
+ mSavedStrongReporters = nullptr;
+ mSavedWeakReporters = nullptr;
+
+ mIsRegistrationBlocked = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetVsize(int64_t* aVsize) {
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+ return VsizeDistinguishedAmount(aVsize);
+#else
+ *aVsize = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) {
+#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER
+ return VsizeMaxContiguousDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResident(int64_t* aAmount) {
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+ return ResidentDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) {
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+ return ResidentFastDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+/*static*/
+int64_t nsMemoryReporterManager::ResidentFast() {
+#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
+ int64_t amount;
+ nsresult rv = ResidentFastDistinguishedAmount(&amount);
+ NS_ENSURE_SUCCESS(rv, 0);
+ return amount;
+#else
+ return 0;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResidentPeak(int64_t* aAmount) {
+#ifdef HAVE_RESIDENT_PEAK_REPORTER
+ return ResidentPeakDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+/*static*/
+int64_t nsMemoryReporterManager::ResidentPeak() {
+#ifdef HAVE_RESIDENT_PEAK_REPORTER
+ int64_t amount = 0;
+ nsresult rv = ResidentPeakDistinguishedAmount(&amount);
+ NS_ENSURE_SUCCESS(rv, 0);
+ return amount;
+#else
+ return 0;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount) {
+#ifdef HAVE_RESIDENT_UNIQUE_REPORTER
+ return ResidentUniqueDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+#ifdef XP_MACOSX
+/*static*/
+int64_t nsMemoryReporterManager::PhysicalFootprint(mach_port_t aPort) {
+ int64_t amount = 0;
+ nsresult rv = PhysicalFootprintAmount(&amount, aPort);
+ NS_ENSURE_SUCCESS(rv, 0);
+ return amount;
+}
+#endif
+
+typedef
+#ifdef XP_WIN
+ HANDLE
+#elif XP_MACOSX
+ mach_port_t
+#elif XP_LINUX
+ pid_t
+#else
+ int /*dummy type */
+#endif
+ ResidentUniqueArg;
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+
+/*static*/
+int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg aProcess) {
+ int64_t amount = 0;
+ nsresult rv = ResidentUniqueDistinguishedAmount(&amount, aProcess);
+ NS_ENSURE_SUCCESS(rv, 0);
+ return amount;
+}
+
+#else
+
+/*static*/
+int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg) {
+# ifdef HAVE_RESIDENT_UNIQUE_REPORTER
+ int64_t amount = 0;
+ nsresult rv = ResidentUniqueDistinguishedAmount(&amount);
+ NS_ENSURE_SUCCESS(rv, 0);
+ return amount;
+# else
+ return 0;
+# endif
+}
+
+#endif // XP_{WIN, MACOSX, LINUX, *}
+
+#ifdef HAVE_JEMALLOC_STATS
+// static
+size_t nsMemoryReporterManager::HeapAllocated(const jemalloc_stats_t& aStats) {
+ return aStats.allocated;
+}
+#endif
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) {
+#ifdef HAVE_JEMALLOC_STATS
+ jemalloc_stats_t stats;
+ jemalloc_stats(&stats);
+ *aAmount = HeapAllocated(stats);
+ return NS_OK;
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+// This has UNITS_PERCENTAGE, so it is multiplied by 100x.
+NS_IMETHODIMP
+nsMemoryReporterManager::GetHeapOverheadFraction(int64_t* aAmount) {
+#ifdef HAVE_JEMALLOC_STATS
+ jemalloc_stats_t stats;
+ jemalloc_stats(&stats);
+ *aAmount = HeapOverheadFraction(stats);
+ return NS_OK;
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+[[nodiscard]] static nsresult GetInfallibleAmount(InfallibleAmountFn aAmountFn,
+ int64_t* aAmount) {
+ if (aAmountFn) {
+ *aAmount = aAmountFn();
+ return NS_OK;
+ }
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem,
+ aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser,
+ aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeRealmsSystem(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsSystem, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetJSMainRuntimeRealmsUser(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsUser, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed,
+ aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) {
+ return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) {
+#ifdef HAVE_PAGE_FAULT_REPORTERS
+ return PageFaultsHardDistinguishedAmount(aAmount);
+#else
+ *aAmount = 0;
+ return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) {
+ void* p = malloc(16);
+ if (!p) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ size_t usable = moz_malloc_usable_size(p);
+ free(p);
+ *aHas = !!(usable > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetIsDMDEnabled(bool* aIsEnabled) {
+#ifdef MOZ_DMD
+ *aIsEnabled = true;
+#else
+ *aIsEnabled = false;
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::GetIsDMDRunning(bool* aIsRunning) {
+#ifdef MOZ_DMD
+ *aIsRunning = dmd::IsRunning();
+#else
+ *aIsRunning = false;
+#endif
+ return NS_OK;
+}
+
+namespace {
+
+/**
+ * This runnable lets us implement
+ * nsIMemoryReporterManager::MinimizeMemoryUsage(). We fire a heap-minimize
+ * notification, spin the event loop, and repeat this process a few times.
+ *
+ * When this sequence finishes, we invoke the callback function passed to the
+ * runnable's constructor.
+ */
+class MinimizeMemoryUsageRunnable : public Runnable {
+ public:
+ explicit MinimizeMemoryUsageRunnable(nsIRunnable* aCallback)
+ : mozilla::Runnable("MinimizeMemoryUsageRunnable"),
+ mCallback(aCallback),
+ mRemainingIters(sNumIters) {}
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mRemainingIters == 0) {
+ os->NotifyObservers(nullptr, "after-minimize-memory-usage",
+ u"MinimizeMemoryUsageRunnable");
+ if (mCallback) {
+ mCallback->Run();
+ }
+ return NS_OK;
+ }
+
+ os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize");
+ mRemainingIters--;
+ NS_DispatchToMainThread(this);
+
+ return NS_OK;
+ }
+
+ private:
+ // Send sNumIters heap-minimize notifications, spinning the event
+ // loop after each notification (see bug 610166 comment 12 for an
+ // explanation), because one notification doesn't cut it.
+ static const uint32_t sNumIters = 3;
+
+ nsCOMPtr<nsIRunnable> mCallback;
+ uint32_t mRemainingIters;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) {
+ RefPtr<MinimizeMemoryUsageRunnable> runnable =
+ new MinimizeMemoryUsageRunnable(aCallback);
+
+ return NS_DispatchToMainThread(runnable);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::SizeOfTab(mozIDOMWindowProxy* aTopWindow,
+ int64_t* aJSObjectsSize,
+ int64_t* aJSStringsSize,
+ int64_t* aJSOtherSize, int64_t* aDomSize,
+ int64_t* aStyleSize, int64_t* aOtherSize,
+ int64_t* aTotalSize, double* aJSMilliseconds,
+ double* aNonJSMilliseconds) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aTopWindow);
+ auto* piWindow = nsPIDOMWindowOuter::From(aTopWindow);
+ if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ TimeStamp t1 = TimeStamp::Now();
+
+ // Measure JS memory consumption (and possibly some non-JS consumption, via
+ // |jsPrivateSize|).
+ size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize;
+ nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), &jsObjectsSize,
+ &jsStringsSize, &jsPrivateSize, &jsOtherSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ TimeStamp t2 = TimeStamp::Now();
+
+ // Measure non-JS memory consumption.
+ size_t domSize, styleSize, otherSize;
+ rv = mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ TimeStamp t3 = TimeStamp::Now();
+
+ *aTotalSize = 0;
+#define DO(aN, n) \
+ { \
+ *aN = (n); \
+ *aTotalSize += (n); \
+ }
+ DO(aJSObjectsSize, jsObjectsSize);
+ DO(aJSStringsSize, jsStringsSize);
+ DO(aJSOtherSize, jsOtherSize);
+ DO(aDomSize, jsPrivateSize + domSize);
+ DO(aStyleSize, styleSize);
+ DO(aOtherSize, otherSize);
+#undef DO
+
+ *aJSMilliseconds = (t2 - t1).ToMilliseconds();
+ *aNonJSMilliseconds = (t3 - t2).ToMilliseconds();
+
+ return NS_OK;
+}
+
+namespace mozilla {
+
+#define GET_MEMORY_REPORTER_MANAGER(mgr) \
+ RefPtr<nsMemoryReporterManager> mgr = \
+ nsMemoryReporterManager::GetOrCreate(); \
+ if (!mgr) { \
+ return NS_ERROR_FAILURE; \
+ }
+
+nsresult RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) {
+ // Hold a strong reference to the argument to make sure it gets released if
+ // we return early below.
+ nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->RegisterStrongReporter(reporter);
+}
+
+nsresult RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter) {
+ // Hold a strong reference to the argument to make sure it gets released if
+ // we return early below.
+ nsCOMPtr<nsIMemoryReporter> reporter = aReporter;
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->RegisterStrongAsyncReporter(reporter);
+}
+
+nsresult RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) {
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->RegisterWeakReporter(aReporter);
+}
+
+nsresult RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter) {
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->RegisterWeakAsyncReporter(aReporter);
+}
+
+nsresult UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter) {
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->UnregisterStrongReporter(aReporter);
+}
+
+nsresult UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) {
+ GET_MEMORY_REPORTER_MANAGER(mgr)
+ return mgr->UnregisterWeakReporter(aReporter);
+}
+
+// Macro for generating functions that register distinguished amount functions
+// with the memory reporter manager.
+#define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \
+ nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) { \
+ GET_MEMORY_REPORTER_MANAGER(mgr) \
+ mgr->mAmountFns.m##name = aAmountFn; \
+ return NS_OK; \
+ }
+
+// Macro for generating functions that unregister distinguished amount
+// functions with the memory reporter manager.
+#define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \
+ nsresult Unregister##name##DistinguishedAmount() { \
+ GET_MEMORY_REPORTER_MANAGER(mgr) \
+ mgr->mAmountFns.m##name = nullptr; \
+ return NS_OK; \
+ }
+
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap)
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak)
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible,
+ JSMainRuntimeCompartmentsSystem)
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsSystem)
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser)
+
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed)
+DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed)
+
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite)
+DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite)
+
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
+
+DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)
+
+#undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT
+#undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT
+
+#define DEFINE_REGISTER_SIZE_OF_TAB(name) \
+ nsresult Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) { \
+ GET_MEMORY_REPORTER_MANAGER(mgr) \
+ mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \
+ return NS_OK; \
+ }
+
+DEFINE_REGISTER_SIZE_OF_TAB(JS);
+DEFINE_REGISTER_SIZE_OF_TAB(NonJS);
+
+#undef DEFINE_REGISTER_SIZE_OF_TAB
+
+#undef GET_MEMORY_REPORTER_MANAGER
+
+} // namespace mozilla
diff --git a/xpcom/base/nsMemoryReporterManager.h b/xpcom/base/nsMemoryReporterManager.h
new file mode 100644
index 0000000000..7064524521
--- /dev/null
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMemoryReporterManager_h__
+#define nsMemoryReporterManager_h__
+
+#include "mozilla/Mutex.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupports.h"
+#include "nsServiceManagerUtils.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif // XP_WIN
+
+#if defined(MOZ_MEMORY)
+# define HAVE_JEMALLOC_STATS 1
+# include "mozmemory.h"
+#endif // MOZ_MEMORY
+
+namespace mozilla {
+class MemoryReportingProcess;
+namespace dom {
+class MemoryReport;
+} // namespace dom
+} // namespace mozilla
+
+class mozIDOMWindowProxy;
+class nsIEventTarget;
+class nsIRunnable;
+class nsITimer;
+
+class nsMemoryReporterManager final : public nsIMemoryReporterManager,
+ public nsIMemoryReporter {
+ virtual ~nsMemoryReporterManager();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTERMANAGER
+ NS_DECL_NSIMEMORYREPORTER
+
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ nsMemoryReporterManager();
+
+ // Gets the memory reporter manager service.
+ static already_AddRefed<nsMemoryReporterManager> GetOrCreate() {
+ nsCOMPtr<nsIMemoryReporterManager> imgr =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+ return imgr.forget().downcast<nsMemoryReporterManager>();
+ }
+
+ typedef nsTHashMap<nsRefPtrHashKey<nsIMemoryReporter>, bool>
+ StrongReportersTable;
+ typedef nsTHashMap<nsPtrHashKey<nsIMemoryReporter>, bool> WeakReportersTable;
+
+ // Inter-process memory reporting proceeds as follows.
+ //
+ // - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER)
+ // synchronously gets memory reports for the current process, sets up some
+ // state (mPendingProcessesState) for when child processes report back --
+ // including a timer -- and starts telling child processes to get memory
+ // reports. Control then returns to the main event loop.
+ //
+ // The number of concurrent child process reports is limited by the pref
+ // "memory.report_concurrency" in order to prevent the memory overhead of
+ // memory reporting from causing problems, especially on B2G when swapping
+ // to compressed RAM; see bug 1154053.
+ //
+ // - HandleChildReport() is called (asynchronously) once per child process
+ // reporter callback.
+ //
+ // - EndProcessReport() is called (asynchronously) once per process that
+ // finishes reporting back, including the parent. If all processes do so
+ // before time-out, the timer is cancelled. If there are child processes
+ // whose requests have not yet been sent, they will be started until the
+ // concurrency limit is (again) reached.
+ //
+ // - TimeoutCallback() is called (asynchronously) if all the child processes
+ // don't respond within the time threshold.
+ //
+ // - FinishReporting() finishes things off. It is *always* called -- either
+ // from EndChildReport() (if all child processes have reported back) or
+ // from TimeoutCallback() (if time-out occurs).
+ //
+ // All operations occur on the main thread.
+ //
+ // The above sequence of steps is a "request". A partially-completed request
+ // is described as "in flight".
+ //
+ // Each request has a "generation", a unique number that identifies it. This
+ // is used to ensure that each reports from a child process corresponds to
+ // the appropriate request from the parent process. (It's easier to
+ // implement a generation system than to implement a child report request
+ // cancellation mechanism.)
+ //
+ // Failures are mostly ignored, because it's (a) typically the most sensible
+ // thing to do, and (b) often hard to do anything else. The following are
+ // the failure cases of note.
+ //
+ // - If a request is made while the previous request is in flight, the new
+ // request is ignored, as per getReports()'s specification. No error is
+ // reported, because the previous request will complete soon enough.
+ //
+ // - If one or more child processes fail to respond within the time limit,
+ // things will proceed as if they don't exist. No error is reported,
+ // because partial information is better than nothing.
+ //
+ // - If a child process reports after the time-out occurs, it is ignored.
+ // (Generation checking will ensure it is ignored even if a subsequent
+ // request is in flight; this is the main use of generations.) No error
+ // is reported, because there's nothing sensible to be done about it at
+ // this late stage.
+ //
+ // - If the time-out occurs after a child process has sent some reports but
+ // before it has signaled completion (see bug 1151597), then what it
+ // successfully sent will be included, with no explicit indication that it
+ // is incomplete.
+ //
+ // Now, what what happens if a child process is created/destroyed in the
+ // middle of a request? Well, PendingProcessesState is initialized with an
+ // array of child process actors as of when the report started. So...
+ //
+ // - If a process is created after reporting starts, it won't be sent a
+ // request for reports. So the reported data will reflect how things were
+ // when the request began.
+ //
+ // - If a process is destroyed before it starts reporting back, the reported
+ // data will reflect how things are when the request ends.
+ //
+ // - If a process is destroyed after it starts reporting back but before it
+ // finishes, the reported data will contain a partial report for it.
+ //
+ // - If a process is destroyed after reporting back, but before all other
+ // child processes have reported back, it will be included in the reported
+ // data. So the reported data will reflect how things were when the
+ // request began.
+ //
+ // The inconsistencies between these cases are unfortunate but difficult to
+ // avoid. It's enough of an edge case to not be worth doing more.
+ //
+ void HandleChildReport(uint32_t aGeneration,
+ const mozilla::dom::MemoryReport& aChildReport);
+ void EndProcessReport(uint32_t aGeneration, bool aSuccess);
+
+ // Functions that (a) implement distinguished amounts, and (b) are outside of
+ // this module.
+ struct AmountFns {
+ mozilla::InfallibleAmountFn mJSMainRuntimeGCHeap = nullptr;
+ mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak = nullptr;
+ mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem = nullptr;
+ mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser = nullptr;
+ mozilla::InfallibleAmountFn mJSMainRuntimeRealmsSystem = nullptr;
+ mozilla::InfallibleAmountFn mJSMainRuntimeRealmsUser = nullptr;
+
+ mozilla::InfallibleAmountFn mImagesContentUsedUncompressed = nullptr;
+
+ mozilla::InfallibleAmountFn mStorageSQLite = nullptr;
+
+ mozilla::InfallibleAmountFn mLowMemoryEventsPhysical = nullptr;
+
+ mozilla::InfallibleAmountFn mGhostWindows = nullptr;
+ };
+ AmountFns mAmountFns;
+
+ // Convenience function to get RSS easily from other code. This is useful
+ // when debugging transient memory spikes with printf instrumentation.
+ static int64_t ResidentFast();
+
+ // Convenience function to get peak RSS easily from other code.
+ static int64_t ResidentPeak();
+
+ // Convenience function to get USS easily from other code. This is useful
+ // when debugging unshared memory pages for forked processes.
+ //
+ // Returns 0 if, for some reason, the resident unique memory cannot be
+ // determined - typically if there is a race between us and someone else
+ // closing the process and we lost that race.
+#ifdef XP_WIN
+ static int64_t ResidentUnique(HANDLE aProcess = nullptr);
+#elif XP_MACOSX
+ // On MacOS this can sometimes be significantly slow. It should not be used
+ // except in debugging or at the request of a user (eg about:memory).
+ static int64_t ResidentUnique(mach_port_t aPort = 0);
+#else
+ static int64_t ResidentUnique(pid_t aPid = 0);
+#endif // XP_{WIN, MACOSX, LINUX, *}
+
+#ifdef XP_MACOSX
+ // Retrive the "phys_footprint" memory statistic on MacOS.
+ static int64_t PhysicalFootprint(mach_port_t aPort = 0);
+#endif
+
+ // Functions that measure per-tab memory consumption.
+ struct SizeOfTabFns {
+ mozilla::JSSizeOfTabFn mJS = nullptr;
+ mozilla::NonJSSizeOfTabFn mNonJS = nullptr;
+ };
+ SizeOfTabFns mSizeOfTabFns;
+
+#ifdef HAVE_JEMALLOC_STATS
+ // These C++ only versions of HeapAllocated and HeapOverheadFraction avoid
+ // extra calls to jemalloc_stats;
+ static size_t HeapAllocated(const jemalloc_stats_t& stats);
+ static int64_t HeapOverheadFraction(const jemalloc_stats_t& stats);
+#endif
+
+ private:
+ bool IsRegistrationBlocked() MOZ_EXCLUDES(mMutex) {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mIsRegistrationBlocked;
+ }
+
+ [[nodiscard]] nsresult RegisterReporterHelper(nsIMemoryReporter* aReporter,
+ bool aForce, bool aStrongRef,
+ bool aIsAsync);
+
+ [[nodiscard]] nsresult StartGettingReports();
+ // No [[nodiscard]] here because ignoring the result is common and reasonable.
+ nsresult FinishReporting();
+
+ void DispatchReporter(nsIMemoryReporter* aReporter, bool aIsAsync,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandleReportData, bool aAnonymize);
+
+ static void TimeoutCallback(nsITimer* aTimer, void* aData);
+ // Note: this timeout needs to be long enough to allow for the
+ // possibility of DMD reports and/or running on a low-end phone.
+ static const uint32_t kTimeoutLengthMS = 180000;
+
+ mozilla::Mutex mMutex;
+ bool mIsRegistrationBlocked MOZ_GUARDED_BY(mMutex);
+
+ StrongReportersTable* mStrongReporters MOZ_GUARDED_BY(mMutex);
+ WeakReportersTable* mWeakReporters MOZ_GUARDED_BY(mMutex);
+
+ // These two are only used for testing purposes.
+ StrongReportersTable* mSavedStrongReporters MOZ_GUARDED_BY(mMutex);
+ WeakReportersTable* mSavedWeakReporters MOZ_GUARDED_BY(mMutex);
+
+ uint32_t mNextGeneration; // MainThread only
+
+ // Used to keep track of state of which processes are currently running and
+ // waiting to run memory reports. Holds references to parameters needed when
+ // requesting a memory report and finishing reporting.
+ struct PendingProcessesState {
+ uint32_t mGeneration;
+ bool mAnonymize;
+ bool mMinimize;
+ nsCOMPtr<nsITimer> mTimer;
+ nsTArray<RefPtr<mozilla::MemoryReportingProcess>> mChildrenPending;
+ uint32_t mNumProcessesRunning;
+ uint32_t mNumProcessesCompleted;
+ uint32_t mConcurrencyLimit;
+ nsCOMPtr<nsIHandleReportCallback> mHandleReport;
+ nsCOMPtr<nsISupports> mHandleReportData;
+ nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
+ nsCOMPtr<nsISupports> mFinishReportingData;
+ nsString mDMDDumpIdent;
+
+ PendingProcessesState(uint32_t aGeneration, bool aAnonymize, bool aMinimize,
+ uint32_t aConcurrencyLimit,
+ nsIHandleReportCallback* aHandleReport,
+ nsISupports* aHandleReportData,
+ nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData,
+ const nsAString& aDMDDumpIdent);
+ };
+
+ // Used to keep track of the state of the asynchronously run memory
+ // reporters. The callback and file handle used when all memory reporters
+ // have finished are also stored here.
+ struct PendingReportersState {
+ // Number of memory reporters currently running.
+ uint32_t mReportsPending;
+
+ // Callback for when all memory reporters have completed.
+ nsCOMPtr<nsIFinishReportingCallback> mFinishReporting;
+ nsCOMPtr<nsISupports> mFinishReportingData;
+
+ // File handle to write a DMD report to if requested.
+ FILE* mDMDFile;
+
+ PendingReportersState(nsIFinishReportingCallback* aFinishReporting,
+ nsISupports* aFinishReportingData, FILE* aDMDFile)
+ : mReportsPending(0),
+ mFinishReporting(aFinishReporting),
+ mFinishReportingData(aFinishReportingData),
+ mDMDFile(aDMDFile) {}
+ };
+
+ // When this is non-null, a request is in flight. Note: We use manual
+ // new/delete for this because its lifetime doesn't match block scope or
+ // anything like that.
+ PendingProcessesState* mPendingProcessesState; // MainThread only
+
+ // This is reinitialized each time a call to GetReports is initiated.
+ PendingReportersState* mPendingReportersState; // MainThread only
+
+ // Used in GetHeapAllocatedAsync() to run jemalloc_stats async.
+ nsCOMPtr<nsIEventTarget> mThreadPool MOZ_GUARDED_BY(mMutex);
+
+ PendingProcessesState* GetStateForGeneration(uint32_t aGeneration);
+ [[nodiscard]] static bool StartChildReport(
+ mozilla::MemoryReportingProcess* aChild,
+ const PendingProcessesState* aState);
+};
+
+#define NS_MEMORY_REPORTER_MANAGER_CID \
+ { \
+ 0xfb97e4f5, 0x32dd, 0x497a, { \
+ 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 \
+ } \
+ }
+
+#endif // nsMemoryReporterManager_h__
diff --git a/xpcom/base/nsObjCExceptions.h b/xpcom/base/nsObjCExceptions.h
new file mode 100644
index 0000000000..e8c2059d8e
--- /dev/null
+++ b/xpcom/base/nsObjCExceptions.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsObjCExceptions_h_
+#define nsObjCExceptions_h_
+
+// Undo the damage that exception_defines.h does.
+#undef try
+#undef catch
+
+@class NSException;
+
+// See Mozilla bug 163260.
+// This file can only be included in an Objective-C context.
+
+void nsObjCExceptionLog(NSException* aException);
+
+namespace mozilla {
+
+// Check if this is an exception that's outside of our control.
+// Called when an exception bubbles up to the native event loop.
+bool ShouldIgnoreObjCException(NSException* aException);
+
+} // namespace mozilla
+
+// For wrapping blocks of Obj-C calls which are not expected to throw exception.
+// Causes a MOZ_CRASH if an Obj-C exception is encountered.
+#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try {
+#define NS_OBJC_END_TRY_ABORT_BLOCK \
+ } \
+ @catch (NSException * _exn) { \
+ nsObjCExceptionLog(_exn); \
+ MOZ_CRASH("Encountered unexpected Objective C exception"); \
+ }
+
+// For wrapping blocks of Obj-C calls. Logs the exception and moves on.
+#define NS_OBJC_BEGIN_TRY_IGNORE_BLOCK @try {
+#define NS_OBJC_END_TRY_IGNORE_BLOCK \
+ } \
+ @catch (NSException * _exn) { \
+ nsObjCExceptionLog(_exn); \
+ }
+
+// Same as above IGNORE_BLOCK but returns a value after the try/catch block.
+#define NS_OBJC_BEGIN_TRY_BLOCK_RETURN @try {
+#define NS_OBJC_END_TRY_BLOCK_RETURN(_rv) \
+ } \
+ @catch (NSException * _exn) { \
+ nsObjCExceptionLog(_exn); \
+ } \
+ return _rv;
+
+#endif // nsObjCExceptions_h_
diff --git a/xpcom/base/nsObjCExceptions.mm b/xpcom/base/nsObjCExceptions.mm
new file mode 100644
index 0000000000..77a7edc998
--- /dev/null
+++ b/xpcom/base/nsObjCExceptions.mm
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsObjCExceptions.h"
+
+#import <Foundation/Foundation.h>
+
+#include <unistd.h>
+#include <signal.h>
+
+#include "nsICrashReporter.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsError.h"
+
+void nsObjCExceptionLog(NSException* aException) {
+ NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", [aException name],
+ [aException reason]);
+
+ // Attach exception info to the crash report.
+ nsCOMPtr<nsICrashReporter> crashReporter =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashReporter) {
+ crashReporter->AppendObjCExceptionInfoToAppNotes(
+ static_cast<void*>(aException));
+ }
+
+#ifdef DEBUG
+ NSLog(@"Stack trace:\n%@", [aException callStackSymbols]);
+#endif
+}
+
+namespace mozilla {
+
+bool ShouldIgnoreObjCException(NSException* aException) {
+ // Ignore known exceptions that we've seen in crash reports, which shouldn't
+ // cause a MOZ_CRASH in Nightly.
+ if (aException.name == NSInternalInconsistencyException) {
+ if ([aException.reason containsString:@"Missing Touches."]) {
+ // Seen in bug 1694000.
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/nsQueryObject.h b/xpcom/base/nsQueryObject.h
new file mode 100644
index 0000000000..7cfbe954ce
--- /dev/null
+++ b/xpcom/base/nsQueryObject.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsQueryObject_h
+#define nsQueryObject_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+
+/*****************************************************************************/
+
+template <class T>
+class MOZ_STACK_CLASS nsQueryObject final : public nsCOMPtr_helper {
+ public:
+ explicit nsQueryObject(T* aRawPtr) : mRawPtr(aRawPtr) {}
+
+ virtual nsresult NS_FASTCALL operator()(const nsIID& aIID,
+ void** aResult) const override {
+ nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult)
+ : NS_ERROR_NULL_POINTER;
+ return status;
+ }
+
+ private:
+ T* MOZ_NON_OWNING_REF mRawPtr;
+};
+
+template <class T>
+class MOZ_STACK_CLASS nsQueryObjectWithError final : public nsCOMPtr_helper {
+ public:
+ nsQueryObjectWithError(T* aRawPtr, nsresult* aErrorPtr)
+ : mRawPtr(aRawPtr), mErrorPtr(aErrorPtr) {}
+
+ virtual nsresult NS_FASTCALL operator()(const nsIID& aIID,
+ void** aResult) const override {
+ nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult)
+ : NS_ERROR_NULL_POINTER;
+ if (mErrorPtr) {
+ *mErrorPtr = status;
+ }
+ return status;
+ }
+
+ private:
+ T* MOZ_NON_OWNING_REF mRawPtr;
+ nsresult* mErrorPtr;
+};
+
+/*****************************************************************************/
+
+/*****************************************************************************/
+
+template <class T>
+inline nsQueryObject<T> do_QueryObject(T* aRawPtr) {
+ return nsQueryObject<T>(aRawPtr);
+}
+
+template <class T>
+inline nsQueryObject<T> do_QueryObject(const nsCOMPtr<T>& aRawPtr) {
+ return nsQueryObject<T>(aRawPtr);
+}
+
+template <class T>
+inline nsQueryObject<T> do_QueryObject(const RefPtr<T>& aRawPtr) {
+ return nsQueryObject<T>(aRawPtr);
+}
+
+template <class T>
+inline nsQueryObjectWithError<T> do_QueryObject(T* aRawPtr,
+ nsresult* aErrorPtr) {
+ return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr);
+}
+
+template <class T>
+inline nsQueryObjectWithError<T> do_QueryObject(const nsCOMPtr<T>& aRawPtr,
+ nsresult* aErrorPtr) {
+ return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr);
+}
+
+template <class T>
+inline nsQueryObjectWithError<T> do_QueryObject(const RefPtr<T>& aRawPtr,
+ nsresult* aErrorPtr) {
+ return nsQueryObjectWithError<T>(aRawPtr, aErrorPtr);
+}
+
+/*****************************************************************************/
+
+#endif // !defined(nsQueryObject_h)
diff --git a/xpcom/base/nsSecurityConsoleMessage.cpp b/xpcom/base/nsSecurityConsoleMessage.cpp
new file mode 100644
index 0000000000..84074b57bc
--- /dev/null
+++ b/xpcom/base/nsSecurityConsoleMessage.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSecurityConsoleMessage.h"
+
+NS_IMPL_ISUPPORTS(nsSecurityConsoleMessage, nsISecurityConsoleMessage)
+
+nsSecurityConsoleMessage::nsSecurityConsoleMessage() = default;
+
+nsSecurityConsoleMessage::~nsSecurityConsoleMessage() = default;
+
+NS_IMETHODIMP
+nsSecurityConsoleMessage::GetTag(nsAString& aTag) {
+ aTag = mTag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecurityConsoleMessage::SetTag(const nsAString& aTag) {
+ mTag = aTag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecurityConsoleMessage::GetCategory(nsAString& aCategory) {
+ aCategory = mCategory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSecurityConsoleMessage::SetCategory(const nsAString& aCategory) {
+ mCategory = aCategory;
+ return NS_OK;
+}
diff --git a/xpcom/base/nsSecurityConsoleMessage.h b/xpcom/base/nsSecurityConsoleMessage.h
new file mode 100644
index 0000000000..9a74d3e38b
--- /dev/null
+++ b/xpcom/base/nsSecurityConsoleMessage.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSecurityConsoleMessage_h__
+#define nsSecurityConsoleMessage_h__
+#include "nsISecurityConsoleMessage.h"
+#include "nsString.h"
+
+class nsSecurityConsoleMessage final : public nsISecurityConsoleMessage {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISECURITYCONSOLEMESSAGE
+
+ nsSecurityConsoleMessage();
+
+ private:
+ ~nsSecurityConsoleMessage();
+
+ protected:
+ nsString mTag;
+ nsString mCategory;
+};
+
+#define NS_SECURITY_CONSOLE_MESSAGE_CID \
+ { \
+ 0x43ebf210, 0x8a7b, 0x4ddb, { \
+ 0xa8, 0x3d, 0xb8, 0x7c, 0x51, 0xa0, 0x58, 0xdb \
+ } \
+ }
+#endif // nsSecurityConsoleMessage_h__
diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp
new file mode 100644
index 0000000000..2b567bda98
--- /dev/null
+++ b/xpcom/base/nsSystemInfo.cpp
@@ -0,0 +1,1683 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsAppRunner.h"
+#include "nsSystemInfo.h"
+#include "prsystem.h"
+#include "prio.h"
+#include "mozilla/SSE.h"
+#include "mozilla/arm.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Try.h"
+#include "jsapi.h"
+#include "js/PropertyAndElement.h" // JS_SetProperty
+#include "mozilla/dom/Promise.h"
+
+#ifdef XP_WIN
+# include <comutil.h>
+# include <time.h>
+# ifndef __MINGW32__
+# include <iwscapi.h>
+# endif // __MINGW32__
+# include <windows.h>
+# include <winioctl.h>
+# ifndef __MINGW32__
+# include <wrl.h>
+# include <wscapi.h>
+# endif // __MINGW32__
+# include "base/scoped_handle_win.h"
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+# include "mozilla/WindowsVersion.h"
+# include "nsAppDirectoryServiceDefs.h"
+# include "nsDirectoryServiceDefs.h"
+# include "nsDirectoryServiceUtils.h"
+# include "nsWindowsHelpers.h"
+# include "WinUtils.h"
+# include "mozilla/NotNull.h"
+
+#endif
+
+#ifdef XP_MACOSX
+# include "MacHelpers.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+# include <gtk/gtk.h>
+# include <dlfcn.h>
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+#if defined(XP_LINUX) && !defined(ANDROID)
+# include <unistd.h>
+# include <fstream>
+# include "mozilla/Tokenizer.h"
+# include "mozilla/widget/LSBUtils.h"
+# include "nsCharSeparatedTokenizer.h"
+
+# include <map>
+# include <string>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidBuild.h"
+# include "mozilla/java/GeckoAppShellWrappers.h"
+# include "mozilla/jni/Utils.h"
+#endif
+
+#ifdef XP_MACOSX
+# include <sys/sysctl.h>
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxInfo.h"
+#endif
+
+// Slot for NS_InitXPCOM to pass information to nsSystemInfo::Init.
+// Only set to nonzero (potentially) if XP_UNIX. On such systems, the
+// system call to discover the appropriate value is not thread-safe,
+// so we must call it before going multithreaded, but nsSystemInfo::Init
+// only happens well after that point.
+uint32_t nsSystemInfo::gUserUmask = 0;
+
+using namespace mozilla::dom;
+
+#if defined(XP_WIN)
+# define RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy \
+ L"Windows.System.Profile.WindowsIntegrityPolicy"
+# ifndef __MINGW32__
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+# endif // __MINGW32__
+#endif
+
+#if defined(XP_LINUX) && !defined(ANDROID)
+static void SimpleParseKeyValuePairs(
+ const std::string& aFilename,
+ std::map<nsCString, nsCString>& aKeyValuePairs) {
+ std::ifstream input(aFilename.c_str());
+ for (std::string line; std::getline(input, line);) {
+ nsAutoCString key, value;
+
+ nsCCharSeparatedTokenizer tokens(nsDependentCString(line.c_str()), ':');
+ if (tokens.hasMoreTokens()) {
+ key = tokens.nextToken();
+ if (tokens.hasMoreTokens()) {
+ value = tokens.nextToken();
+ }
+ // We want the value even if there was just one token, to cover the
+ // case where we had the key, and the value was blank (seems to be
+ // a valid scenario some files.)
+ aKeyValuePairs[key] = value;
+ }
+ }
+}
+#endif
+
+#ifdef XP_WIN
+// Lifted from media/webrtc/trunk/webrtc/base/systeminfo.cc,
+// so keeping the _ instead of switching to camel case for now.
+static void GetProcessorInformation(int* physical_cpus, int* cache_size_L2,
+ int* cache_size_L3) {
+ MOZ_ASSERT(physical_cpus && cache_size_L2 && cache_size_L3);
+
+ *physical_cpus = 0;
+ *cache_size_L2 = 0; // This will be in kbytes
+ *cache_size_L3 = 0; // This will be in kbytes
+
+ // Determine buffer size, allocate and get processor information.
+ // Size can change between calls (unlikely), so a loop is done.
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION info_buffer[32];
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION* infos = &info_buffer[0];
+ DWORD return_length = sizeof(info_buffer);
+ while (!::GetLogicalProcessorInformation(infos, &return_length)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
+ infos == &info_buffer[0]) {
+ infos = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION
+ [return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)];
+ } else {
+ return;
+ }
+ }
+
+ for (size_t i = 0;
+ i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
+ if (infos[i].Relationship == RelationProcessorCore) {
+ ++*physical_cpus;
+ } else if (infos[i].Relationship == RelationCache) {
+ // Only care about L2 and L3 cache
+ switch (infos[i].Cache.Level) {
+ case 2:
+ *cache_size_L2 = static_cast<int>(infos[i].Cache.Size / 1024);
+ break;
+ case 3:
+ *cache_size_L3 = static_cast<int>(infos[i].Cache.Size / 1024);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (infos != &info_buffer[0]) {
+ delete[] infos;
+ }
+ return;
+}
+#endif
+
+#if defined(XP_WIN)
+namespace {
+static nsresult GetFolderDiskInfo(nsIFile* file, FolderDiskInfo& info) {
+ info.model.Truncate();
+ info.revision.Truncate();
+ info.isSSD = false;
+
+ nsAutoString filePath;
+ nsresult rv = file->GetPath(filePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ wchar_t volumeMountPoint[MAX_PATH] = {L'\\', L'\\', L'.', L'\\'};
+ const size_t PREFIX_LEN = 4;
+ if (!::GetVolumePathNameW(
+ filePath.get(), volumeMountPoint + PREFIX_LEN,
+ mozilla::ArrayLength(volumeMountPoint) - PREFIX_LEN)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ size_t volumeMountPointLen = wcslen(volumeMountPoint);
+ // Since we would like to open a drive and not a directory, we need to
+ // remove any trailing backslash. A drive handle is valid for
+ // DeviceIoControl calls, a directory handle is not.
+ if (volumeMountPoint[volumeMountPointLen - 1] == L'\\') {
+ volumeMountPoint[volumeMountPointLen - 1] = L'\0';
+ }
+ ScopedHandle handle(::CreateFileW(volumeMountPoint, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
+ OPEN_EXISTING, 0, nullptr));
+ if (!handle.IsValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ STORAGE_PROPERTY_QUERY queryParameters = {StorageDeviceProperty,
+ PropertyStandardQuery};
+ STORAGE_DEVICE_DESCRIPTOR outputHeader = {sizeof(STORAGE_DEVICE_DESCRIPTOR)};
+ DWORD bytesRead = 0;
+ if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters,
+ sizeof(queryParameters), &outputHeader,
+ sizeof(outputHeader), &bytesRead, nullptr)) {
+ return NS_ERROR_FAILURE;
+ }
+ PSTORAGE_DEVICE_DESCRIPTOR deviceOutput =
+ (PSTORAGE_DEVICE_DESCRIPTOR)malloc(outputHeader.Size);
+ if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters,
+ sizeof(queryParameters), deviceOutput,
+ outputHeader.Size, &bytesRead, nullptr)) {
+ free(deviceOutput);
+ return NS_ERROR_FAILURE;
+ }
+
+ queryParameters.PropertyId = StorageDeviceTrimProperty;
+ bytesRead = 0;
+ bool isSSD = false;
+ DEVICE_TRIM_DESCRIPTOR trimDescriptor = {sizeof(DEVICE_TRIM_DESCRIPTOR)};
+ if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters,
+ sizeof(queryParameters), &trimDescriptor,
+ sizeof(trimDescriptor), &bytesRead, nullptr)) {
+ if (trimDescriptor.TrimEnabled) {
+ isSSD = true;
+ }
+ }
+
+ if (isSSD) {
+ // Get Seek Penalty
+ queryParameters.PropertyId = StorageDeviceSeekPenaltyProperty;
+ bytesRead = 0;
+ DEVICE_SEEK_PENALTY_DESCRIPTOR seekPenaltyDescriptor = {
+ sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR)};
+ if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY,
+ &queryParameters, sizeof(queryParameters),
+ &seekPenaltyDescriptor, sizeof(seekPenaltyDescriptor),
+ &bytesRead, nullptr)) {
+ // It is possible that the disk has TrimEnabled, but also
+ // IncursSeekPenalty; In this case, this is an HDD
+ if (seekPenaltyDescriptor.IncursSeekPenalty) {
+ isSSD = false;
+ }
+ }
+ }
+
+ // Some HDDs are including product ID info in the vendor field. Since PNP
+ // IDs include vendor info and product ID concatenated together, we'll do
+ // that here and interpret the result as a unique ID for the HDD model.
+ if (deviceOutput->VendorIdOffset) {
+ info.model =
+ reinterpret_cast<char*>(deviceOutput) + deviceOutput->VendorIdOffset;
+ }
+ if (deviceOutput->ProductIdOffset) {
+ info.model +=
+ reinterpret_cast<char*>(deviceOutput) + deviceOutput->ProductIdOffset;
+ }
+ info.model.CompressWhitespace();
+ if (deviceOutput->ProductRevisionOffset) {
+ info.revision = reinterpret_cast<char*>(deviceOutput) +
+ deviceOutput->ProductRevisionOffset;
+ info.revision.CompressWhitespace();
+ }
+ info.isSSD = isSSD;
+ free(deviceOutput);
+ return NS_OK;
+}
+
+static nsresult CollectDiskInfo(nsIFile* greDir, nsIFile* winDir,
+ nsIFile* profDir, DiskInfo& info) {
+ nsresult rv = GetFolderDiskInfo(greDir, info.binary);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = GetFolderDiskInfo(winDir, info.system);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return GetFolderDiskInfo(profDir, info.profile);
+}
+
+static nsresult CollectOSInfo(OSInfo& info) {
+ HKEY installYearHKey;
+ LONG status = RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0,
+ KEY_READ | KEY_WOW64_64KEY, &installYearHKey);
+
+ if (status != ERROR_SUCCESS) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoRegKey installYearKey(installYearHKey);
+
+ DWORD type = 0;
+ time_t raw_time = 0;
+ DWORD time_size = sizeof(time_t);
+
+ status = RegQueryValueExW(installYearHKey, L"InstallDate", nullptr, &type,
+ (LPBYTE)&raw_time, &time_size);
+
+ if (status != ERROR_SUCCESS) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (type != REG_DWORD) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ tm time;
+ if (localtime_s(&time, &raw_time) != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ info.installYear = 1900UL + time.tm_year;
+
+ nsAutoServiceHandle scm(
+ OpenSCManager(nullptr, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT));
+
+ if (!scm) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool superfetchServiceRunning = false;
+
+ // Superfetch was introduced in Windows Vista as a service with the name
+ // SysMain. The service display name was also renamed to SysMain after Windows
+ // 10 build 1809.
+ nsAutoServiceHandle hService(OpenService(scm, L"SysMain", GENERIC_READ));
+
+ if (hService) {
+ SERVICE_STATUS superfetchStatus;
+ LPSERVICE_STATUS pSuperfetchStatus = &superfetchStatus;
+
+ if (!QueryServiceStatus(hService, pSuperfetchStatus)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ superfetchServiceRunning =
+ superfetchStatus.dwCurrentState == SERVICE_RUNNING;
+ }
+
+ // If the SysMain (Superfetch) service is available, but not configured using
+ // the defaults, then it's disabled for our purposes, since it's not going to
+ // be operating as expected.
+ bool superfetchUsingDefaultParams = true;
+ bool prefetchUsingDefaultParams = true;
+
+ static const WCHAR prefetchParamsKeyName[] =
+ L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory "
+ L"Management\\PrefetchParameters";
+ static const DWORD SUPERFETCH_DEFAULT_PARAM = 3;
+ static const DWORD PREFETCH_DEFAULT_PARAM = 3;
+
+ HKEY prefetchParamsHKey;
+
+ LONG prefetchParamsStatus =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefetchParamsKeyName, 0,
+ KEY_READ | KEY_WOW64_64KEY, &prefetchParamsHKey);
+
+ if (prefetchParamsStatus == ERROR_SUCCESS) {
+ DWORD valueSize = sizeof(DWORD);
+ DWORD superfetchValue = 0;
+ nsAutoRegKey prefetchParamsKey(prefetchParamsHKey);
+ LONG superfetchParamStatus = RegQueryValueExW(
+ prefetchParamsHKey, L"EnableSuperfetch", nullptr, &type,
+ reinterpret_cast<LPBYTE>(&superfetchValue), &valueSize);
+
+ // If the EnableSuperfetch registry key doesn't exist, then it's using the
+ // default configuration.
+ if (superfetchParamStatus == ERROR_SUCCESS &&
+ superfetchValue != SUPERFETCH_DEFAULT_PARAM) {
+ superfetchUsingDefaultParams = false;
+ }
+
+ DWORD prefetchValue = 0;
+
+ LONG prefetchParamStatus = RegQueryValueExW(
+ prefetchParamsHKey, L"EnablePrefetcher", nullptr, &type,
+ reinterpret_cast<LPBYTE>(&prefetchValue), &valueSize);
+
+ // If the EnablePrefetcher registry key doesn't exist, then we interpret
+ // that as the Prefetcher being disabled (since Prefetch behaviour when
+ // the key is not available appears to be undefined).
+ if (prefetchParamStatus != ERROR_SUCCESS ||
+ prefetchValue != PREFETCH_DEFAULT_PARAM) {
+ prefetchUsingDefaultParams = false;
+ }
+ }
+
+ info.hasSuperfetch = superfetchServiceRunning && superfetchUsingDefaultParams;
+ info.hasPrefetch = prefetchUsingDefaultParams;
+
+ return NS_OK;
+}
+
+nsresult CollectCountryCode(nsAString& aCountryCode) {
+ GEOID geoid = GetUserGeoID(GEOCLASS_NATION);
+ if (geoid == GEOID_NOT_AVAILABLE) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // Get required length
+ int numChars = GetGeoInfoW(geoid, GEO_ISO2, nullptr, 0, 0);
+ if (!numChars) {
+ return NS_ERROR_FAILURE;
+ }
+ // Now get the string for real
+ aCountryCode.SetLength(numChars);
+ numChars =
+ GetGeoInfoW(geoid, GEO_ISO2, char16ptr_t(aCountryCode.BeginWriting()),
+ aCountryCode.Length(), 0);
+ if (!numChars) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // numChars includes null terminator
+ aCountryCode.Truncate(numChars - 1);
+ return NS_OK;
+}
+
+} // namespace
+
+# ifndef __MINGW32__
+
+static HRESULT EnumWSCProductList(
+ nsAString& aOutput, mozilla::NotNull<IWSCProductList*> aProdList) {
+ MOZ_ASSERT(aOutput.IsEmpty());
+
+ LONG count;
+ HRESULT hr = aProdList->get_Count(&count);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ for (LONG index = 0; index < count; ++index) {
+ RefPtr<IWscProduct> product;
+ hr = aProdList->get_Item(index, getter_AddRefs(product));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ WSC_SECURITY_PRODUCT_STATE state;
+ hr = product->get_ProductState(&state);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // We only care about products that are active
+ if (state == WSC_SECURITY_PRODUCT_STATE_OFF ||
+ state == WSC_SECURITY_PRODUCT_STATE_SNOOZED) {
+ continue;
+ }
+
+ _bstr_t bName;
+ hr = product->get_ProductName(bName.GetAddress());
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (!aOutput.IsEmpty()) {
+ aOutput.AppendLiteral(u";");
+ }
+
+ aOutput.Append((wchar_t*)bName, bName.length());
+ }
+
+ return S_OK;
+}
+
+static nsresult GetWindowsSecurityCenterInfo(nsAString& aAVInfo,
+ nsAString& aAntiSpyInfo,
+ nsAString& aFirewallInfo) {
+ aAVInfo.Truncate();
+ aAntiSpyInfo.Truncate();
+ aFirewallInfo.Truncate();
+
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const CLSID clsid = __uuidof(WSCProductList);
+ const IID iid = __uuidof(IWSCProductList);
+
+ // NB: A separate instance of IWSCProductList is needed for each distinct
+ // security provider type; MSDN says that we cannot reuse the same object
+ // and call Initialize() to pave over the previous data.
+
+ WSC_SECURITY_PROVIDER providerTypes[] = {WSC_SECURITY_PROVIDER_ANTIVIRUS,
+ WSC_SECURITY_PROVIDER_ANTISPYWARE,
+ WSC_SECURITY_PROVIDER_FIREWALL};
+
+ // Each output must match the corresponding entry in providerTypes.
+ nsAString* outputs[] = {&aAVInfo, &aAntiSpyInfo, &aFirewallInfo};
+
+ static_assert(
+ mozilla::ArrayLength(providerTypes) == mozilla::ArrayLength(outputs),
+ "Length of providerTypes and outputs arrays must match");
+
+ for (uint32_t index = 0; index < mozilla::ArrayLength(providerTypes);
+ ++index) {
+ RefPtr<IWSCProductList> prodList;
+ HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid,
+ getter_AddRefs(prodList));
+ if (FAILED(hr)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ hr = prodList->Initialize(providerTypes[index]);
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ hr = EnumWSCProductList(*outputs[index],
+ mozilla::WrapNotNull(prodList.get()));
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+# endif // __MINGW32__
+
+#endif // defined(XP_WIN)
+
+#ifdef XP_MACOSX
+static nsresult GetAppleModelId(nsAutoCString& aModelId) {
+ size_t numChars = 0;
+ size_t result = sysctlbyname("hw.model", nullptr, &numChars, nullptr, 0);
+ if (result != 0 || !numChars) {
+ return NS_ERROR_FAILURE;
+ }
+ aModelId.SetLength(numChars);
+ result =
+ sysctlbyname("hw.model", aModelId.BeginWriting(), &numChars, nullptr, 0);
+ if (result != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ // numChars includes null terminator
+ aModelId.Truncate(numChars - 1);
+ return NS_OK;
+}
+
+static nsresult ProcessIsRosettaTranslated(bool& isRosetta) {
+# if defined(__aarch64__)
+ // There is no need to call sysctlbyname() if we are running as arm64.
+ isRosetta = false;
+# else
+ int ret = 0;
+ size_t size = sizeof(ret);
+ if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
+ if (errno != ENOENT) {
+ fprintf(stderr, "Failed to check for translation environment\n");
+ }
+ isRosetta = false;
+ } else {
+ isRosetta = (ret == 1);
+ }
+# endif
+ return NS_OK;
+}
+#endif
+
+using namespace mozilla;
+
+nsSystemInfo::nsSystemInfo() = default;
+
+nsSystemInfo::~nsSystemInfo() = default;
+
+// CPU-specific information.
+static const struct PropItems {
+ const char* name;
+ bool (*propfun)(void);
+} cpuPropItems[] = {
+ // x86-specific bits.
+ {"hasMMX", mozilla::supports_mmx},
+ {"hasSSE", mozilla::supports_sse},
+ {"hasSSE2", mozilla::supports_sse2},
+ {"hasSSE3", mozilla::supports_sse3},
+ {"hasSSSE3", mozilla::supports_ssse3},
+ {"hasSSE4A", mozilla::supports_sse4a},
+ {"hasSSE4_1", mozilla::supports_sse4_1},
+ {"hasSSE4_2", mozilla::supports_sse4_2},
+ {"hasAVX", mozilla::supports_avx},
+ {"hasAVX2", mozilla::supports_avx2},
+ {"hasAES", mozilla::supports_aes},
+ // ARM-specific bits.
+ {"hasEDSP", mozilla::supports_edsp},
+ {"hasARMv6", mozilla::supports_armv6},
+ {"hasARMv7", mozilla::supports_armv7},
+ {"hasNEON", mozilla::supports_neon}};
+
+nsresult CollectProcessInfo(ProcessInfo& info) {
+ nsAutoCString cpuVendor;
+ nsAutoCString cpuName;
+ int cpuSpeed = -1;
+ int cpuFamily = -1;
+ int cpuModel = -1;
+ int cpuStepping = -1;
+ int logicalCPUs = -1;
+ int physicalCPUs = -1;
+ int cacheSizeL2 = -1;
+ int cacheSizeL3 = -1;
+
+#if defined(XP_WIN)
+ // IsWow64Process2 is only available on Windows 10+, so we have to dynamically
+ // check for its existence.
+ typedef BOOL(WINAPI * LPFN_IWP2)(HANDLE, USHORT*, USHORT*);
+ LPFN_IWP2 iwp2 = reinterpret_cast<LPFN_IWP2>(
+ GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process2"));
+ BOOL isWow64 = FALSE;
+ USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN;
+ USHORT nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
+ BOOL gotWow64Value;
+ if (iwp2) {
+ gotWow64Value = iwp2(GetCurrentProcess(), &processMachine, &nativeMachine);
+ if (gotWow64Value) {
+ isWow64 = (processMachine != IMAGE_FILE_MACHINE_UNKNOWN);
+ }
+ } else {
+ gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64);
+ // The function only indicates a WOW64 environment if it's 32-bit x86
+ // running on x86-64, so emulate what IsWow64Process2 would have given.
+ if (gotWow64Value && isWow64) {
+ processMachine = IMAGE_FILE_MACHINE_I386;
+ nativeMachine = IMAGE_FILE_MACHINE_AMD64;
+ }
+ }
+ NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed");
+ if (gotWow64Value) {
+ // Set this always, even for the x86-on-arm64 case.
+ info.isWow64 = !!isWow64;
+ // Additional information if we're running x86-on-arm64
+ info.isWowARM64 = (processMachine == IMAGE_FILE_MACHINE_I386 &&
+ nativeMachine == IMAGE_FILE_MACHINE_ARM64);
+ }
+
+ // S Mode
+
+# ifndef __MINGW32__
+ // WindowsIntegrityPolicy is only available on newer versions
+ // of Windows 10, so there's no point in trying to check this
+ // on earlier versions. We know GetActivationFactory crashes on
+ // Windows 7 when trying to retrieve this class, and may also
+ // crash on very old versions of Windows 10.
+ if (IsWin10Sep2018UpdateOrLater()) {
+ ComPtr<IWindowsIntegrityPolicyStatics> wip;
+ HRESULT hr = GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy)
+ .Get(),
+ &wip);
+ if (SUCCEEDED(hr)) {
+ // info.isWindowsSMode ends up true if Windows is in S mode, otherwise
+ // false
+ // https://docs.microsoft.com/en-us/uwp/api/windows.system.profile.windowsintegritypolicy.isenabled?view=winrt-22000
+ hr = wip->get_IsEnabled(&info.isWindowsSMode);
+ NS_WARNING_ASSERTION(SUCCEEDED(hr),
+ "WindowsIntegrityPolicy.IsEnabled failed");
+ }
+ }
+# endif // __MINGW32__
+
+ // CPU speed
+ HKEY key;
+ static const WCHAR keyName[] =
+ L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
+
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, &key) ==
+ ERROR_SUCCESS) {
+ DWORD data, len, vtype;
+ len = sizeof(data);
+
+ if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
+ &len) == ERROR_SUCCESS) {
+ cpuSpeed = static_cast<int>(data);
+ }
+
+ // Limit to 64 double byte characters, should be plenty, but create
+ // a buffer one larger as the result may not be null terminated. If
+ // it is more than 64, we will not get the value.
+ wchar_t cpuVendorStr[64 + 1];
+ len = sizeof(cpuVendorStr) - 2;
+ if (RegQueryValueExW(key, L"VendorIdentifier", 0, &vtype,
+ reinterpret_cast<LPBYTE>(cpuVendorStr),
+ &len) == ERROR_SUCCESS &&
+ vtype == REG_SZ && len % 2 == 0 && len > 1) {
+ cpuVendorStr[len / 2] = 0; // In case it isn't null terminated
+ CopyUTF16toUTF8(nsDependentString(cpuVendorStr), cpuVendor);
+ }
+
+ // Limit to 64 double byte characters, should be plenty, but create
+ // a buffer one larger as the result may not be null terminated. If
+ // it is more than 64, we will not get the value.
+ // The expected string size is 48 characters or less.
+ wchar_t cpuNameStr[64 + 1];
+ len = sizeof(cpuNameStr) - 2;
+ if (RegQueryValueExW(key, L"ProcessorNameString", 0, &vtype,
+ reinterpret_cast<LPBYTE>(cpuNameStr),
+ &len) == ERROR_SUCCESS &&
+ vtype == REG_SZ && len % 2 == 0 && len > 1) {
+ cpuNameStr[len / 2] = 0; // In case it isn't null terminated
+ CopyUTF16toUTF8(nsDependentString(cpuNameStr), cpuName);
+ }
+
+ RegCloseKey(key);
+ }
+
+ // Other CPU attributes:
+ SYSTEM_INFO si;
+ GetNativeSystemInfo(&si);
+ logicalCPUs = si.dwNumberOfProcessors;
+ GetProcessorInformation(&physicalCPUs, &cacheSizeL2, &cacheSizeL3);
+ if (physicalCPUs <= 0) {
+ physicalCPUs = logicalCPUs;
+ }
+ cpuFamily = si.wProcessorLevel;
+ cpuModel = si.wProcessorRevision >> 8;
+ cpuStepping = si.wProcessorRevision & 0xFF;
+#elif defined(XP_MACOSX)
+ // CPU speed
+ uint64_t sysctlValue64 = 0;
+ uint32_t sysctlValue32 = 0;
+ size_t len = 0;
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.cpufrequency_max", &sysctlValue64, &len, NULL, 0)) {
+ cpuSpeed = static_cast<int>(sysctlValue64 / 1000000);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) {
+ physicalCPUs = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) {
+ logicalCPUs = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) {
+ cacheSizeL2 = static_cast<int>(sysctlValue64 / 1024);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) {
+ cacheSizeL3 = static_cast<int>(sysctlValue64 / 1024);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ if (!sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0)) {
+ char* cpuVendorStr = new char[len];
+ if (!sysctlbyname("machdep.cpu.vendor", cpuVendorStr, &len, NULL, 0)) {
+ cpuVendor = cpuVendorStr;
+ }
+ delete[] cpuVendorStr;
+ }
+
+ if (!sysctlbyname("machdep.cpu.brand_string", NULL, &len, NULL, 0)) {
+ char* cpuNameStr = new char[len];
+ if (!sysctlbyname("machdep.cpu.brand_string", cpuNameStr, &len, NULL, 0)) {
+ cpuName = cpuNameStr;
+ }
+ delete[] cpuNameStr;
+ }
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.family", &sysctlValue32, &len, NULL, 0)) {
+ cpuFamily = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) {
+ cpuModel = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) {
+ cpuStepping = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+#elif defined(XP_LINUX) && !defined(ANDROID)
+ // Get vendor, family, model, stepping, physical cores
+ // from /proc/cpuinfo file
+ {
+ std::map<nsCString, nsCString> keyValuePairs;
+ SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs);
+
+ // cpuVendor from "vendor_id"
+ info.cpuVendor.Assign(keyValuePairs["vendor_id"_ns]);
+
+ // cpuName from "model name"
+ info.cpuName.Assign(keyValuePairs["model name"_ns]);
+
+ {
+ // cpuFamily from "cpu family"
+ Tokenizer::Token t;
+ Tokenizer p(keyValuePairs["cpu family"_ns]);
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cpuFamily = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+
+ {
+ // cpuModel from "model"
+ Tokenizer::Token t;
+ Tokenizer p(keyValuePairs["model"_ns]);
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cpuModel = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+
+ {
+ // cpuStepping from "stepping"
+ Tokenizer::Token t;
+ Tokenizer p(keyValuePairs["stepping"_ns]);
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cpuStepping = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+
+ {
+ // physicalCPUs from "cpu cores"
+ Tokenizer::Token t;
+ Tokenizer p(keyValuePairs["cpu cores"_ns]);
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ physicalCPUs = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+ }
+
+ {
+ // Get cpuSpeed from another file.
+ std::ifstream input(
+ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq");
+ std::string line;
+ if (getline(input, line)) {
+ Tokenizer::Token t;
+ Tokenizer p(line.c_str());
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cpuSpeed = static_cast<int32_t>(t.AsInteger() / 1000);
+ }
+ }
+ }
+
+ {
+ // Get cacheSizeL2 from yet another file
+ std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index2/size");
+ std::string line;
+ if (getline(input, line)) {
+ Tokenizer::Token t;
+ Tokenizer p(line.c_str(), nullptr, "K");
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cacheSizeL2 = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+ }
+
+ {
+ // Get cacheSizeL3 from yet another file
+ std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index3/size");
+ std::string line;
+ if (getline(input, line)) {
+ Tokenizer::Token t;
+ Tokenizer p(line.c_str(), nullptr, "K");
+ if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER &&
+ t.AsInteger() <= INT32_MAX) {
+ cacheSizeL3 = static_cast<int32_t>(t.AsInteger());
+ }
+ }
+ }
+
+ info.cpuCount = PR_GetNumberOfProcessors();
+#else
+ info.cpuCount = PR_GetNumberOfProcessors();
+#endif
+
+ if (cpuSpeed >= 0) {
+ info.cpuSpeed = cpuSpeed;
+ } else {
+ info.cpuSpeed = 0;
+ }
+ if (!cpuVendor.IsEmpty()) {
+ info.cpuVendor = cpuVendor;
+ }
+ if (!cpuName.IsEmpty()) {
+ info.cpuName = cpuName;
+ }
+ if (cpuFamily >= 0) {
+ info.cpuFamily = cpuFamily;
+ }
+ if (cpuModel >= 0) {
+ info.cpuModel = cpuModel;
+ }
+ if (cpuStepping >= 0) {
+ info.cpuStepping = cpuStepping;
+ }
+
+ if (logicalCPUs >= 0) {
+ info.cpuCount = logicalCPUs;
+ }
+ if (physicalCPUs >= 0) {
+ info.cpuCores = physicalCPUs;
+ }
+
+ if (cacheSizeL2 >= 0) {
+ info.l2cacheKB = cacheSizeL2;
+ }
+ if (cacheSizeL3 >= 0) {
+ info.l3cacheKB = cacheSizeL3;
+ }
+
+ return NS_OK;
+}
+
+#if defined(__MINGW32__)
+WINBASEAPI
+BOOL WINAPI IsUserCetAvailableInEnvironment(_In_ DWORD UserCetEnvironment);
+
+# define USER_CET_ENVIRONMENT_WIN32_PROCESS 0x00000000
+#endif
+
+nsresult nsSystemInfo::Init() {
+ // check that it is called from the main thread on all platforms.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ static const struct {
+ PRSysInfo cmd;
+ const char* name;
+ } items[] = {{PR_SI_SYSNAME, "name"},
+ {PR_SI_ARCHITECTURE, "arch"},
+ {PR_SI_RELEASE, "version"},
+ {PR_SI_RELEASE_BUILD, "build"}};
+
+ for (uint32_t i = 0; i < (sizeof(items) / sizeof(items[0])); i++) {
+ char buf[SYS_INFO_BUFFER_LENGTH];
+ if (PR_GetSystemInfo(items[i].cmd, buf, sizeof(buf)) == PR_SUCCESS) {
+ rv = SetPropertyAsACString(NS_ConvertASCIItoUTF16(items[i].name),
+ nsDependentCString(buf));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_WARNING("PR_GetSystemInfo failed");
+ }
+ }
+
+ SetPropertyAsBool(u"isPackagedApp"_ns, false);
+
+ // Additional informations not available through PR_GetSystemInfo.
+ SetInt32Property(u"pagesize"_ns, PR_GetPageSize());
+ SetInt32Property(u"pageshift"_ns, PR_GetPageShift());
+ SetInt32Property(u"memmapalign"_ns, PR_GetMemMapAlignment());
+ SetUint64Property(u"memsize"_ns, PR_GetPhysicalMemorySize());
+ SetUint32Property(u"umask"_ns, nsSystemInfo::gUserUmask);
+
+#ifdef HAVE_64BIT_BUILD
+ SetUint32Property(u"archbits"_ns, 64);
+#else
+ SetUint32Property(u"archbits"_ns, 32);
+#endif
+
+ uint64_t virtualMem = 0;
+
+#if defined(XP_WIN)
+ // Virtual memory:
+ MEMORYSTATUSEX memStat;
+ memStat.dwLength = sizeof(memStat);
+ if (GlobalMemoryStatusEx(&memStat)) {
+ virtualMem = memStat.ullTotalVirtual;
+ }
+#endif
+ if (virtualMem) SetUint64Property(u"virtualmemsize"_ns, virtualMem);
+
+ for (uint32_t i = 0; i < ArrayLength(cpuPropItems); i++) {
+ rv = SetPropertyAsBool(NS_ConvertASCIItoUTF16(cpuPropItems[i].name),
+ cpuPropItems[i].propfun());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+#ifdef XP_WIN
+ bool isMinGW =
+# ifdef __MINGW32__
+ true;
+# else
+ false;
+# endif
+ rv = SetPropertyAsBool(u"isMinGW"_ns, !!isMinGW);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ boolean hasPackageIdentity = widget::WinUtils::HasPackageIdentity();
+
+ rv = SetPropertyAsBool(u"hasWinPackageId"_ns, hasPackageIdentity);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetPropertyAsAString(u"winPackageFamilyName"_ns,
+ widget::WinUtils::GetPackageFamilyName());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = SetPropertyAsBool(u"isPackagedApp"_ns, hasPackageIdentity);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+# ifndef __MINGW32__
+ nsAutoString avInfo, antiSpyInfo, firewallInfo;
+ if (NS_SUCCEEDED(
+ GetWindowsSecurityCenterInfo(avInfo, antiSpyInfo, firewallInfo))) {
+ if (!avInfo.IsEmpty()) {
+ rv = SetPropertyAsAString(u"registeredAntiVirus"_ns, avInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (!antiSpyInfo.IsEmpty()) {
+ rv = SetPropertyAsAString(u"registeredAntiSpyware"_ns, antiSpyInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ if (!firewallInfo.IsEmpty()) {
+ rv = SetPropertyAsAString(u"registeredFirewall"_ns, firewallInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+# endif // __MINGW32__
+
+ mozilla::DynamicallyLinkedFunctionPtr<
+ decltype(&IsUserCetAvailableInEnvironment)>
+ isUserCetAvailable(L"api-ms-win-core-sysinfo-l1-2-6.dll",
+ "IsUserCetAvailableInEnvironment");
+ bool hasUserCET = isUserCetAvailable &&
+ isUserCetAvailable(USER_CET_ENVIRONMENT_WIN32_PROCESS);
+ rv = SetPropertyAsBool(u"hasUserCET"_ns, hasUserCET);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString pointerExplanation;
+ widget::WinUtils::GetPointerExplanation(&pointerExplanation);
+ rv = SetPropertyAsAString(u"pointingDevices"_ns, pointerExplanation);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#endif
+
+#if defined(XP_MACOSX)
+ nsAutoCString modelId;
+ if (NS_SUCCEEDED(GetAppleModelId(modelId))) {
+ rv = SetPropertyAsACString(u"appleModelId"_ns, modelId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bool isRosetta;
+ if (NS_SUCCEEDED(ProcessIsRosettaTranslated(isRosetta))) {
+ rv = SetPropertyAsBool(u"rosettaStatus"_ns, isRosetta);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+
+ {
+ nsAutoCString themeInfo;
+ LookAndFeel::GetThemeInfo(themeInfo);
+ MOZ_TRY(SetPropertyAsACString(u"osThemeInfo"_ns, themeInfo));
+ }
+
+#if defined(MOZ_WIDGET_GTK)
+ // This must be done here because NSPR can only separate OS's when compiled,
+ // not libraries. 64 bytes is going to be well enough for "GTK " followed by 3
+ // integers separated with dots.
+ char gtkver[64];
+ ssize_t gtkver_len = 0;
+
+ if (gtkver_len <= 0) {
+ gtkver_len = SprintfLiteral(gtkver, "GTK %u.%u.%u", gtk_major_version,
+ gtk_minor_version, gtk_micro_version);
+ }
+
+ nsAutoCString secondaryLibrary;
+ if (gtkver_len > 0 && gtkver_len < int(sizeof(gtkver))) {
+ secondaryLibrary.Append(nsDependentCSubstring(gtkver, gtkver_len));
+ }
+
+# ifndef MOZ_TSAN
+ // With TSan, avoid loading libpulse here because we cannot unload it
+ // afterwards due to restrictions from TSan about unloading libraries
+ // matched by the suppression list.
+ void* libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
+ const char* libpulseVersion = "not-available";
+ if (libpulse) {
+ auto pa_get_library_version = reinterpret_cast<const char* (*)()>(
+ dlsym(libpulse, "pa_get_library_version"));
+
+ if (pa_get_library_version) {
+ libpulseVersion = pa_get_library_version();
+ }
+ }
+
+ secondaryLibrary.AppendPrintf(",libpulse %s", libpulseVersion);
+
+ if (libpulse) {
+ dlclose(libpulse);
+ }
+# endif
+
+ rv = SetPropertyAsACString(u"secondaryLibrary"_ns, secondaryLibrary);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = SetPropertyAsBool(u"isPackagedApp"_ns,
+ widget::IsRunningUnderFlatpakOrSnap() ||
+ widget::IsPackagedAppFileExists());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ AndroidSystemInfo info;
+ GetAndroidSystemInfo(&info);
+ SetupAndroidInfo(info);
+#endif
+
+#if defined(XP_LINUX) && !defined(ANDROID)
+ nsCString dist, desc, release, codename;
+ if (widget::lsb::GetLSBRelease(dist, desc, release, codename)) {
+ SetPropertyAsACString(u"distro"_ns, dist);
+ SetPropertyAsACString(u"distroVersion"_ns, release);
+ }
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ SandboxInfo sandInfo = SandboxInfo::Get();
+
+ SetPropertyAsBool(u"hasSeccompBPF"_ns,
+ sandInfo.Test(SandboxInfo::kHasSeccompBPF));
+ SetPropertyAsBool(u"hasSeccompTSync"_ns,
+ sandInfo.Test(SandboxInfo::kHasSeccompTSync));
+ SetPropertyAsBool(u"hasUserNamespaces"_ns,
+ sandInfo.Test(SandboxInfo::kHasUserNamespaces));
+ SetPropertyAsBool(u"hasPrivilegedUserNamespaces"_ns,
+ sandInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces));
+
+ if (sandInfo.Test(SandboxInfo::kEnabledForContent)) {
+ SetPropertyAsBool(u"canSandboxContent"_ns, sandInfo.CanSandboxContent());
+ }
+
+ if (sandInfo.Test(SandboxInfo::kEnabledForMedia)) {
+ SetPropertyAsBool(u"canSandboxMedia"_ns, sandInfo.CanSandboxMedia());
+ }
+#endif // XP_LINUX && MOZ_SANDBOX
+
+ return NS_OK;
+}
+
+#ifdef MOZ_WIDGET_ANDROID
+// Prerelease versions of Android use a letter instead of version numbers.
+// Unfortunately this breaks websites due to the user agent.
+// Chrome works around this by hardcoding an Android version when a
+// numeric version can't be obtained. We're doing the same.
+// This version will need to be updated whenever there is a new official
+// Android release. Search for "kDefaultAndroidMajorVersion" in:
+// https://source.chromium.org/chromium/chromium/src/+/master:base/system/sys_info_android.cc
+# define DEFAULT_ANDROID_VERSION u"10.0.99"
+
+/* static */
+void nsSystemInfo::GetAndroidSystemInfo(AndroidSystemInfo* aInfo) {
+ if (!jni::IsAvailable()) {
+ // called from xpcshell etc.
+ aInfo->sdk_version() = 0;
+ return;
+ }
+
+ jni::String::LocalRef model = java::sdk::Build::MODEL();
+ aInfo->device() = model->ToString();
+
+ jni::String::LocalRef manufacturer =
+ mozilla::java::sdk::Build::MANUFACTURER();
+ aInfo->manufacturer() = manufacturer->ToString();
+
+ jni::String::LocalRef hardware = java::sdk::Build::HARDWARE();
+ aInfo->hardware() = hardware->ToString();
+
+ jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE();
+ nsString str(release->ToString());
+ int major_version;
+ int minor_version;
+ int bugfix_version;
+ int num_read = sscanf(NS_ConvertUTF16toUTF8(str).get(), "%d.%d.%d",
+ &major_version, &minor_version, &bugfix_version);
+ if (num_read == 0) {
+ aInfo->release_version() = nsLiteralString(DEFAULT_ANDROID_VERSION);
+ } else {
+ aInfo->release_version() = str;
+ }
+
+ aInfo->sdk_version() = jni::GetAPIVersion();
+ aInfo->isTablet() = java::GeckoAppShell::IsTablet();
+}
+
+void nsSystemInfo::SetupAndroidInfo(const AndroidSystemInfo& aInfo) {
+ if (!aInfo.device().IsEmpty()) {
+ SetPropertyAsAString(u"device"_ns, aInfo.device());
+ }
+ if (!aInfo.manufacturer().IsEmpty()) {
+ SetPropertyAsAString(u"manufacturer"_ns, aInfo.manufacturer());
+ }
+ if (!aInfo.release_version().IsEmpty()) {
+ SetPropertyAsAString(u"release_version"_ns, aInfo.release_version());
+ }
+ SetPropertyAsBool(u"tablet"_ns, aInfo.isTablet());
+ // NSPR "version" is the kernel version. For Android we want the Android
+ // version. Rename SDK version to version and put the kernel version into
+ // kernel_version.
+ nsAutoString str;
+ nsresult rv = GetPropertyAsAString(u"version"_ns, str);
+ if (NS_SUCCEEDED(rv)) {
+ SetPropertyAsAString(u"kernel_version"_ns, str);
+ }
+ // When JNI is not available (eg. in xpcshell tests), sdk_version is 0.
+ if (aInfo.sdk_version() != 0) {
+ if (!aInfo.hardware().IsEmpty()) {
+ SetPropertyAsAString(u"hardware"_ns, aInfo.hardware());
+ }
+ SetPropertyAsInt32(u"version"_ns, aInfo.sdk_version());
+ }
+}
+#endif // MOZ_WIDGET_ANDROID
+
+void nsSystemInfo::SetInt32Property(const nsAString& aPropertyName,
+ const int32_t aValue) {
+ NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value");
+ if (aValue > 0) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ SetPropertyAsInt32(aPropertyName, aValue);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property");
+ }
+}
+
+void nsSystemInfo::SetUint32Property(const nsAString& aPropertyName,
+ const uint32_t aValue) {
+ // Only one property is currently set via this function.
+ // It may legitimately be zero.
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ SetPropertyAsUint32(aPropertyName, aValue);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property");
+}
+
+void nsSystemInfo::SetUint64Property(const nsAString& aPropertyName,
+ const uint64_t aValue) {
+ NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value");
+ if (aValue > 0) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ SetPropertyAsUint64(aPropertyName, aValue);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property");
+ }
+}
+
+#ifdef XP_WIN
+
+static bool GetJSObjForDiskInfo(JSContext* aCx, JS::Handle<JSObject*> aParent,
+ const FolderDiskInfo& info,
+ const char* propName) {
+ JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx));
+ if (!jsInfo) {
+ return false;
+ }
+
+ JSString* strModel =
+ JS_NewStringCopyN(aCx, info.model.get(), info.model.Length());
+ if (!strModel) {
+ return false;
+ }
+ JS::Rooted<JS::Value> valModel(aCx, JS::StringValue(strModel));
+ if (!JS_SetProperty(aCx, jsInfo, "model", valModel)) {
+ return false;
+ }
+
+ JSString* strRevision =
+ JS_NewStringCopyN(aCx, info.revision.get(), info.revision.Length());
+ if (!strRevision) {
+ return false;
+ }
+ JS::Rooted<JS::Value> valRevision(aCx, JS::StringValue(strRevision));
+ if (!JS_SetProperty(aCx, jsInfo, "revision", valRevision)) {
+ return false;
+ }
+
+ JSString* strSSD = JS_NewStringCopyZ(aCx, info.isSSD ? "SSD" : "HDD");
+ if (!strSSD) {
+ return false;
+ }
+ JS::Rooted<JS::Value> valSSD(aCx, JS::StringValue(strSSD));
+ if (!JS_SetProperty(aCx, jsInfo, "type", valSSD)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> val(aCx, JS::ObjectValue(*jsInfo));
+ return JS_SetProperty(aCx, aParent, propName, val);
+}
+
+JSObject* GetJSObjForOSInfo(JSContext* aCx, const OSInfo& info) {
+ JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> valInstallYear(aCx, JS::Int32Value(info.installYear));
+ JS_SetProperty(aCx, jsInfo, "installYear", valInstallYear);
+
+ JS::Rooted<JS::Value> valHasSuperfetch(aCx,
+ JS::BooleanValue(info.hasSuperfetch));
+ JS_SetProperty(aCx, jsInfo, "hasSuperfetch", valHasSuperfetch);
+
+ JS::Rooted<JS::Value> valHasPrefetch(aCx, JS::BooleanValue(info.hasPrefetch));
+ JS_SetProperty(aCx, jsInfo, "hasPrefetch", valHasPrefetch);
+
+ return jsInfo;
+}
+
+#endif
+
+JSObject* GetJSObjForProcessInfo(JSContext* aCx, const ProcessInfo& info) {
+ JS::Rooted<JSObject*> jsInfo(aCx, JS_NewPlainObject(aCx));
+
+#if defined(XP_WIN)
+ JS::Rooted<JS::Value> valisWow64(aCx, JS::BooleanValue(info.isWow64));
+ JS_SetProperty(aCx, jsInfo, "isWow64", valisWow64);
+
+ JS::Rooted<JS::Value> valisWowARM64(aCx, JS::BooleanValue(info.isWowARM64));
+ JS_SetProperty(aCx, jsInfo, "isWowARM64", valisWowARM64);
+
+ JS::Rooted<JS::Value> valisWindowsSMode(
+ aCx, JS::BooleanValue(info.isWindowsSMode));
+ JS_SetProperty(aCx, jsInfo, "isWindowsSMode", valisWindowsSMode);
+#endif
+
+ JS::Rooted<JS::Value> valCountInfo(aCx, JS::Int32Value(info.cpuCount));
+ JS_SetProperty(aCx, jsInfo, "count", valCountInfo);
+
+ JS::Rooted<JS::Value> valCoreInfo(aCx, JS::Int32Value(info.cpuCores));
+ JS_SetProperty(aCx, jsInfo, "cores", valCoreInfo);
+
+ JSString* strVendor =
+ JS_NewStringCopyN(aCx, info.cpuVendor.get(), info.cpuVendor.Length());
+ JS::Rooted<JS::Value> valVendor(aCx, JS::StringValue(strVendor));
+ JS_SetProperty(aCx, jsInfo, "vendor", valVendor);
+
+ JSString* strName =
+ JS_NewStringCopyN(aCx, info.cpuName.get(), info.cpuName.Length());
+ JS::Rooted<JS::Value> valName(aCx, JS::StringValue(strName));
+ JS_SetProperty(aCx, jsInfo, "name", valName);
+
+ JS::Rooted<JS::Value> valFamilyInfo(aCx, JS::Int32Value(info.cpuFamily));
+ JS_SetProperty(aCx, jsInfo, "family", valFamilyInfo);
+
+ JS::Rooted<JS::Value> valModelInfo(aCx, JS::Int32Value(info.cpuModel));
+ JS_SetProperty(aCx, jsInfo, "model", valModelInfo);
+
+ JS::Rooted<JS::Value> valSteppingInfo(aCx, JS::Int32Value(info.cpuStepping));
+ JS_SetProperty(aCx, jsInfo, "stepping", valSteppingInfo);
+
+ JS::Rooted<JS::Value> valL2CacheInfo(aCx, JS::Int32Value(info.l2cacheKB));
+ JS_SetProperty(aCx, jsInfo, "l2cacheKB", valL2CacheInfo);
+
+ JS::Rooted<JS::Value> valL3CacheInfo(aCx, JS::Int32Value(info.l3cacheKB));
+ JS_SetProperty(aCx, jsInfo, "l3cacheKB", valL3CacheInfo);
+
+ JS::Rooted<JS::Value> valSpeedInfo(aCx, JS::Int32Value(info.cpuSpeed));
+ JS_SetProperty(aCx, jsInfo, "speedMHz", valSpeedInfo);
+
+ return jsInfo;
+}
+
+RefPtr<nsISerialEventTarget> nsSystemInfo::GetBackgroundTarget() {
+ if (!mBackgroundET) {
+ MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
+ "SystemInfoThread", getter_AddRefs(mBackgroundET)));
+ }
+ return mBackgroundET;
+}
+
+NS_IMETHODIMP
+nsSystemInfo::GetOsInfo(JSContext* aCx, Promise** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+#if defined(XP_WIN)
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult erv;
+ RefPtr<Promise> promise = Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return erv.StealNSResult();
+ }
+
+ if (!mOSInfoPromise) {
+ RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget();
+
+ mOSInfoPromise = InvokeAsync(backgroundET, __func__, []() {
+ OSInfo info;
+ nsresult rv = CollectOSInfo(info);
+ if (NS_SUCCEEDED(rv)) {
+ return OSInfoPromise::CreateAndResolve(info, __func__);
+ }
+ return OSInfoPromise::CreateAndReject(rv, __func__);
+ });
+ };
+
+ // Chain the new promise to the extant mozpromise
+ RefPtr<Promise> capturedPromise = promise;
+ mOSInfoPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [capturedPromise](const OSInfo& info) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
+ capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> val(
+ cx, JS::ObjectValue(*GetJSObjForOSInfo(cx, info)));
+ capturedPromise->MaybeResolve(val);
+ },
+ [capturedPromise](const nsresult rv) {
+ // Resolve with null when installYear is not available from the system
+ capturedPromise->MaybeResolve(JS::NullHandleValue);
+ });
+
+ promise.forget(aResult);
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSystemInfo::GetDiskInfo(JSContext* aCx, Promise** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+#ifdef XP_WIN
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+ ErrorResult erv;
+ RefPtr<Promise> promise = Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return erv.StealNSResult();
+ }
+
+ if (!mDiskInfoPromise) {
+ RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget();
+ nsCOMPtr<nsIFile> greDir;
+ nsCOMPtr<nsIFile> winDir;
+ nsCOMPtr<nsIFile> profDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_GetSpecialDirectory(NS_WIN_WINDOWS_DIR, getter_AddRefs(winDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDiskInfoPromise =
+ InvokeAsync(backgroundET, __func__, [greDir, winDir, profDir]() {
+ DiskInfo info;
+ nsresult rv = CollectDiskInfo(greDir, winDir, profDir, info);
+ if (NS_SUCCEEDED(rv)) {
+ return DiskInfoPromise::CreateAndResolve(info, __func__);
+ }
+ return DiskInfoPromise::CreateAndReject(rv, __func__);
+ });
+ }
+
+ // Chain the new promise to the extant mozpromise.
+ RefPtr<Promise> capturedPromise = promise;
+ mDiskInfoPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [capturedPromise](const DiskInfo& info) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
+ capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> jsInfo(cx, JS_NewPlainObject(cx));
+ // Store data in the rv:
+ bool succeededSettingAllObjects =
+ jsInfo && GetJSObjForDiskInfo(cx, jsInfo, info.binary, "binary") &&
+ GetJSObjForDiskInfo(cx, jsInfo, info.profile, "profile") &&
+ GetJSObjForDiskInfo(cx, jsInfo, info.system, "system");
+ // The above can fail due to OOM
+ if (!succeededSettingAllObjects) {
+ JS_ClearPendingException(cx);
+ capturedPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*jsInfo));
+ capturedPromise->MaybeResolve(val);
+ },
+ [capturedPromise](const nsresult rv) {
+ capturedPromise->MaybeReject(rv);
+ });
+
+ promise.forget(aResult);
+#endif
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsSystemInfo, nsHashPropertyBag, nsISystemInfo)
+
+NS_IMETHODIMP
+nsSystemInfo::GetCountryCode(JSContext* aCx, Promise** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+#if defined(XP_MACOSX) || defined(XP_WIN)
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult erv;
+ RefPtr<Promise> promise = Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return erv.StealNSResult();
+ }
+
+ if (!mCountryCodePromise) {
+ RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget();
+
+ mCountryCodePromise = InvokeAsync(backgroundET, __func__, []() {
+ nsAutoString countryCode;
+# ifdef XP_MACOSX
+ nsresult rv = GetSelectedCityInfo(countryCode);
+# endif
+# ifdef XP_WIN
+ nsresult rv = CollectCountryCode(countryCode);
+# endif
+
+ if (NS_SUCCEEDED(rv)) {
+ return CountryCodePromise::CreateAndResolve(countryCode, __func__);
+ }
+ return CountryCodePromise::CreateAndReject(rv, __func__);
+ });
+ }
+
+ RefPtr<Promise> capturedPromise = promise;
+ mCountryCodePromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [capturedPromise](const nsString& countryCode) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
+ capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSString*> jsCountryCode(
+ cx, JS_NewUCStringCopyZ(cx, countryCode.get()));
+
+ JS::Rooted<JS::Value> val(cx, JS::StringValue(jsCountryCode));
+ capturedPromise->MaybeResolve(val);
+ },
+ [capturedPromise](const nsresult rv) {
+ // Resolve with null when countryCode is not available from the system
+ capturedPromise->MaybeResolve(JS::NullHandleValue);
+ });
+
+ promise.forget(aResult);
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSystemInfo::GetProcessInfo(JSContext* aCx, Promise** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult erv;
+ RefPtr<Promise> promise = Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return erv.StealNSResult();
+ }
+
+ if (!mProcessInfoPromise) {
+ RefPtr<nsISerialEventTarget> backgroundET = GetBackgroundTarget();
+
+ mProcessInfoPromise = InvokeAsync(backgroundET, __func__, []() {
+ ProcessInfo info;
+ nsresult rv = CollectProcessInfo(info);
+ if (NS_SUCCEEDED(rv)) {
+ return ProcessInfoPromise::CreateAndResolve(info, __func__);
+ }
+ return ProcessInfoPromise::CreateAndReject(rv, __func__);
+ });
+ };
+
+ // Chain the new promise to the extant mozpromise
+ RefPtr<Promise> capturedPromise = promise;
+ mProcessInfoPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [capturedPromise](const ProcessInfo& info) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) {
+ capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> val(
+ cx, JS::ObjectValue(*GetJSObjForProcessInfo(cx, info)));
+ capturedPromise->MaybeResolve(val);
+ },
+ [capturedPromise](const nsresult rv) {
+ // Resolve with null when installYear is not available from the system
+ capturedPromise->MaybeResolve(JS::NullHandleValue);
+ });
+
+ promise.forget(aResult);
+
+ return NS_OK;
+}
diff --git a/xpcom/base/nsSystemInfo.h b/xpcom/base/nsSystemInfo.h
new file mode 100644
index 0000000000..9d47b5d4f6
--- /dev/null
+++ b/xpcom/base/nsSystemInfo.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSSYSTEMINFO_H_
+#define _NSSYSTEMINFO_H_
+
+#include "nsHashPropertyBag.h"
+#include "nsISystemInfo.h"
+#include "mozilla/MozPromise.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/dom/PContent.h"
+#endif // MOZ_WIDGET_ANDROID
+
+#if defined(XP_WIN)
+# include <inspectable.h>
+
+// The UUID comes from winrt/windows.system.profile.idl
+// in the Windows SDK
+MIDL_INTERFACE("7D1D81DB-8D63-4789-9EA5-DDCF65A94F3C")
+IWindowsIntegrityPolicyStatics : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE get_IsEnabled(bool* value) = 0;
+};
+#endif
+
+class nsISerialEventTarget;
+
+struct FolderDiskInfo {
+ nsCString model;
+ nsCString revision;
+ bool isSSD;
+};
+
+struct DiskInfo {
+ FolderDiskInfo binary;
+ FolderDiskInfo profile;
+ FolderDiskInfo system;
+};
+
+struct OSInfo {
+ uint32_t installYear;
+ bool hasSuperfetch;
+ bool hasPrefetch;
+};
+
+struct ProcessInfo {
+ bool isWow64 = false;
+ bool isWowARM64 = false;
+ // Whether or not the system is Windows 10 or 11 in S Mode.
+ // S Mode existed prior to us being able to query it, so this
+ // is unreliable on Windows versions prior to 1810.
+ bool isWindowsSMode = false;
+ int32_t cpuCount = 0;
+ int32_t cpuCores = 0;
+ nsCString cpuVendor;
+ nsCString cpuName;
+ int32_t cpuFamily = 0;
+ int32_t cpuModel = 0;
+ int32_t cpuStepping = 0;
+ int32_t l2cacheKB = 0;
+ int32_t l3cacheKB = 0;
+ int32_t cpuSpeed = 0;
+};
+
+typedef mozilla::MozPromise<DiskInfo, nsresult, /* IsExclusive */ false>
+ DiskInfoPromise;
+
+typedef mozilla::MozPromise<nsAutoString, nsresult, /* IsExclusive */ false>
+ CountryCodePromise;
+
+typedef mozilla::MozPromise<OSInfo, nsresult, /* IsExclusive */ false>
+ OSInfoPromise;
+
+typedef mozilla::MozPromise<ProcessInfo, nsresult, /* IsExclusive */ false>
+ ProcessInfoPromise;
+
+// Synchronous info collection, avoid calling it from the main thread, consider
+// using the promise-based `nsISystemInfo::GetProcessInfo()` instead.
+// Note that only known fields will be written.
+nsresult CollectProcessInfo(ProcessInfo& info);
+
+class nsSystemInfo final : public nsISystemInfo, public nsHashPropertyBag {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISYSTEMINFO
+
+ nsSystemInfo();
+
+ nsresult Init();
+
+ // Slot for NS_InitXPCOM to pass information to nsSystemInfo::Init.
+ // See comments above the variable definition and in NS_InitXPCOM.
+ static uint32_t gUserUmask;
+
+#ifdef MOZ_WIDGET_ANDROID
+ static void GetAndroidSystemInfo(mozilla::dom::AndroidSystemInfo* aInfo);
+
+ protected:
+ void SetupAndroidInfo(const mozilla::dom::AndroidSystemInfo&);
+#endif
+
+ protected:
+ void SetInt32Property(const nsAString& aPropertyName, const int32_t aValue);
+ void SetUint32Property(const nsAString& aPropertyName, const uint32_t aValue);
+ void SetUint64Property(const nsAString& aPropertyName, const uint64_t aValue);
+
+ private:
+ ~nsSystemInfo();
+
+ RefPtr<DiskInfoPromise> mDiskInfoPromise;
+ RefPtr<CountryCodePromise> mCountryCodePromise;
+ RefPtr<OSInfoPromise> mOSInfoPromise;
+ RefPtr<ProcessInfoPromise> mProcessInfoPromise;
+ RefPtr<nsISerialEventTarget> mBackgroundET;
+ RefPtr<nsISerialEventTarget> GetBackgroundTarget();
+};
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+#define NS_SYSTEMINFO_CID \
+ { \
+ 0xd962398a, 0x99e5, 0x49b2, { \
+ 0x85, 0x7a, 0xc1, 0x59, 0x04, 0x9c, 0x7f, 0x6c \
+ } \
+ }
+
+#endif /* _NSSYSTEMINFO_H_ */
diff --git a/xpcom/base/nsTraceRefcnt.cpp b/xpcom/base/nsTraceRefcnt.cpp
new file mode 100644
index 0000000000..41de68eb31
--- /dev/null
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -0,0 +1,1219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTraceRefcnt.h"
+
+#include "base/process_util.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Path.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPtr.h"
+#include "nsXPCOMPrivate.h"
+#include "nscore.h"
+#include "nsClassHashtable.h"
+#include "nsContentUtils.h"
+#include "nsISupports.h"
+#include "nsHashKeys.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "prenv.h"
+#include "prlink.h"
+#include "nsCRT.h"
+#include <math.h>
+#include "nsHashKeys.h"
+#include "mozilla/StackWalk.h"
+#include "nsThreadUtils.h"
+#include "CodeAddressService.h"
+
+#include "nsXULAppAPI.h"
+#ifdef XP_WIN
+# include <io.h>
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h>
+#endif
+
+#include "mozilla/Atomics.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BlockingResourceBase.h"
+#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/UniquePtr.h"
+
+#include <string>
+#include <vector>
+
+#ifdef HAVE_DLOPEN
+# include <dlfcn.h>
+#endif
+
+#ifdef MOZ_DMD
+# include "nsMemoryInfoDumper.h"
+#endif
+
+// dynamic_cast<void*> is not supported on Windows without RTTI.
+#ifndef _WIN32
+# define HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "prthread.h"
+
+class MOZ_CAPABILITY("mutex") TraceLogMutex
+ : private mozilla::detail::MutexImpl {
+ public:
+ explicit TraceLogMutex() = default;
+
+ private:
+ friend class AutoTraceLogLock;
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { ::mozilla::detail::MutexImpl::lock(); }
+ void Unlock() MOZ_CAPABILITY_RELEASE() {
+ ::mozilla::detail::MutexImpl::unlock();
+ }
+};
+
+static TraceLogMutex gTraceLog;
+
+class MOZ_RAII AutoTraceLogLock {
+ public:
+ explicit AutoTraceLogLock(TraceLogMutex& aMutex) : mMutex(aMutex) {
+ mMutex.Lock();
+ }
+
+ AutoTraceLogLock(const AutoTraceLogLock&) = delete;
+ AutoTraceLogLock& operator=(const AutoTraceLogLock&) = delete;
+ AutoTraceLogLock(AutoTraceLogLock&&) = delete;
+ AutoTraceLogLock& operator=(AutoTraceLogLock&&) = delete;
+
+ ~AutoTraceLogLock() { mMutex.Unlock(); }
+
+ private:
+ TraceLogMutex& mMutex;
+};
+
+class BloatEntry;
+struct SerialNumberRecord;
+
+using mozilla::AutoRestore;
+using mozilla::CodeAddressService;
+using mozilla::CycleCollectedJSContext;
+using mozilla::StaticAutoPtr;
+
+using BloatHash = nsClassHashtable<nsDepCharHashKey, BloatEntry>;
+using CharPtrSet = nsTHashtable<nsCharPtrHashKey>;
+using IntPtrSet = nsTHashtable<IntPtrHashKey>;
+using SerialHash = nsClassHashtable<nsVoidPtrHashKey, SerialNumberRecord>;
+
+static StaticAutoPtr<BloatHash> gBloatView;
+static StaticAutoPtr<CharPtrSet> gTypesToLog;
+static StaticAutoPtr<IntPtrSet> gObjectsToLog;
+static StaticAutoPtr<SerialHash> gSerialNumbers;
+
+static intptr_t gNextSerialNumber;
+static bool gDumpedStatistics = false;
+static bool gLogJSStacks = false;
+
+// By default, debug builds only do bloat logging. Bloat logging
+// only tries to record when an object is created or destroyed, so we
+// optimize the common case in NS_LogAddRef and NS_LogRelease where
+// only bloat logging is enabled and no logging needs to be done.
+enum LoggingType { NoLogging, OnlyBloatLogging, FullLogging };
+
+static LoggingType gLogging;
+
+static bool gLogLeaksOnly;
+
+#define BAD_TLS_INDEX ((unsigned)-1)
+
+// if gActivityTLS == BAD_TLS_INDEX, then we're
+// unitialized... otherwise this points to a NSPR TLS thread index
+// indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then
+// activity is ok, otherwise not!
+static unsigned gActivityTLS = BAD_TLS_INDEX;
+
+static bool gInitialized;
+static nsrefcnt gInitCount;
+
+static FILE* gBloatLog = nullptr;
+static FILE* gRefcntsLog = nullptr;
+static FILE* gAllocLog = nullptr;
+static FILE* gCOMPtrLog = nullptr;
+
+static void WalkTheStackSavingLocations(std::vector<void*>& aLocations,
+ const void* aFirstFramePC);
+
+struct SerialNumberRecord {
+ SerialNumberRecord()
+ : serialNumber(++gNextSerialNumber), refCount(0), COMPtrCount(0) {}
+
+ intptr_t serialNumber;
+ int32_t refCount;
+ int32_t COMPtrCount;
+ // We use std:: classes here rather than the XPCOM equivalents because the
+ // XPCOM equivalents do leak-checking, and if you try to leak-check while
+ // leak-checking, you're gonna have a bad time.
+ std::vector<void*> allocationStack;
+ mozilla::UniquePtr<char[]> jsStack;
+
+ void SaveJSStack() {
+ // If this thread isn't running JS, there's nothing to do.
+ if (!CycleCollectedJSContext::Get()) {
+ return;
+ }
+
+ if (!nsContentUtils::IsInitialized()) {
+ return;
+ }
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return;
+ }
+
+ JS::UniqueChars chars = xpc_PrintJSStack(cx,
+ /*showArgs=*/false,
+ /*showLocals=*/false,
+ /*showThisProps=*/false);
+ size_t len = strlen(chars.get());
+ jsStack = mozilla::MakeUnique<char[]>(len + 1);
+ memcpy(jsStack.get(), chars.get(), len + 1);
+ }
+};
+
+struct nsTraceRefcntStats {
+ uint64_t mCreates;
+ uint64_t mDestroys;
+
+ bool HaveLeaks() const { return mCreates != mDestroys; }
+
+ void Clear() {
+ mCreates = 0;
+ mDestroys = 0;
+ }
+
+ int64_t NumLeaked() const { return (int64_t)(mCreates - mDestroys); }
+};
+
+#ifdef DEBUG
+static void AssertActivityIsLegal(const char* aType, const char* aAction) {
+ if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) {
+ char buf[1024];
+ SprintfLiteral(buf, "XPCOM object %s %s from static ctor/dtor", aType,
+ aAction);
+
+ if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) {
+ MOZ_CRASH_UNSAFE_PRINTF("%s", buf);
+ } else {
+ NS_WARNING(buf);
+ }
+ }
+}
+# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
+ do { \
+ AssertActivityIsLegal(type_, action_); \
+ } while (0)
+#else
+# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
+ do { \
+ } while (0)
+#endif // DEBUG
+
+////////////////////////////////////////////////////////////////////////////////
+
+mozilla::StaticAutoPtr<CodeAddressService<>> gCodeAddressService;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class BloatEntry {
+ public:
+ BloatEntry(const char* aClassName, uint32_t aClassSize)
+ : mClassSize(aClassSize), mStats() {
+ MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty");
+ mClassName = aClassName;
+ mStats.Clear();
+ mTotalLeaked = 0;
+ }
+
+ ~BloatEntry() = default;
+
+ uint32_t GetClassSize() { return (uint32_t)mClassSize; }
+ const char* GetClassName() { return mClassName; }
+
+ void Ctor() { mStats.mCreates++; }
+
+ void Dtor() { mStats.mDestroys++; }
+
+ void Total(BloatEntry* aTotal) {
+ aTotal->mStats.mCreates += mStats.mCreates;
+ aTotal->mStats.mDestroys += mStats.mDestroys;
+ aTotal->mClassSize +=
+ mClassSize * mStats.mCreates; // adjust for average in DumpTotal
+ aTotal->mTotalLeaked += mClassSize * mStats.NumLeaked();
+ }
+
+ void DumpTotal(FILE* aOut) {
+ mClassSize /= mStats.mCreates;
+ Dump(-1, aOut);
+ }
+
+ bool PrintDumpHeader(FILE* aOut, const char* aMsg) {
+ fprintf(aOut, "\n== BloatView: %s, %s process %d\n", aMsg,
+ XRE_GetProcessTypeString(), getpid());
+ if (gLogLeaksOnly && !mStats.HaveLeaks()) {
+ return false;
+ }
+
+ // clang-format off
+ fprintf(aOut,
+ "\n" \
+ " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \
+ " | | Per-Inst Leaked| Total Rem|\n");
+ // clang-format on
+
+ this->DumpTotal(aOut);
+
+ return true;
+ }
+
+ void Dump(int aIndex, FILE* aOut) {
+ if (gLogLeaksOnly && !mStats.HaveLeaks()) {
+ return;
+ }
+
+ if (mStats.HaveLeaks() || mStats.mCreates != 0) {
+ fprintf(aOut,
+ "%4d |%-38.38s| %8d %8" PRId64 "|%8" PRIu64 " %8" PRId64 "|\n",
+ aIndex + 1, mClassName, GetClassSize(),
+ nsCRT::strcmp(mClassName, "TOTAL")
+ ? (mStats.NumLeaked() * GetClassSize())
+ : mTotalLeaked,
+ mStats.mCreates, mStats.NumLeaked());
+ }
+ }
+
+ protected:
+ const char* mClassName;
+ // mClassSize is stored as a double because of the way we compute the avg
+ // class size for total bloat.
+ double mClassSize;
+ // mTotalLeaked is only used for the TOTAL entry.
+ int64_t mTotalLeaked;
+ nsTraceRefcntStats mStats;
+};
+
+static void EnsureBloatView() {
+ if (!gBloatView) {
+ gBloatView = new BloatHash(256);
+ }
+}
+
+static BloatEntry* GetBloatEntry(const char* aTypeName,
+ uint32_t aInstanceSize) {
+ EnsureBloatView();
+ BloatEntry* entry = gBloatView->Get(aTypeName);
+ if (!entry && aInstanceSize > 0) {
+ entry = gBloatView
+ ->InsertOrUpdate(aTypeName, mozilla::MakeUnique<BloatEntry>(
+ aTypeName, aInstanceSize))
+ .get();
+ } else {
+ MOZ_ASSERT(
+ aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize,
+ "Mismatched sizes were recorded in the memory leak logging table. "
+ "The usual cause of this is having a templated class that uses "
+ "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. "
+ "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a "
+ "non-templated base class. Another possible cause is a runnable with "
+ "an mName that matches another refcounted class, or two refcounted "
+ "classes with the same class name in different C++ namespaces.");
+ }
+ return entry;
+}
+
+static void DumpSerialNumbers(const SerialHash::ConstIterator& aHashEntry,
+ FILE* aFd, bool aDumpAsStringBuffer) {
+ SerialNumberRecord* record = aHashEntry.UserData();
+ auto* outputFile = aFd;
+#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+ fprintf(outputFile, "%" PRIdPTR " @%p (%d references; %d from COMPtrs)\n",
+ record->serialNumber, aHashEntry.Key(), record->refCount,
+ record->COMPtrCount);
+#else
+ fprintf(outputFile, "%" PRIdPTR " @%p (%d references)\n",
+ record->serialNumber, aHashEntry.Key(), record->refCount);
+#endif
+
+ if (aDumpAsStringBuffer) {
+ // This output will be wrong if the nsStringBuffer was used to
+ // store a char16_t string.
+ auto* buffer = static_cast<const nsStringBuffer*>(aHashEntry.Key());
+ nsDependentCString bufferString(static_cast<char*>(buffer->Data()));
+ fprintf(outputFile,
+ "Contents of leaked nsStringBuffer with storage size %d as a "
+ "char*: %s\n",
+ buffer->StorageSize(), bufferString.get());
+ }
+
+ if (!record->allocationStack.empty()) {
+ static const size_t bufLen = 1024;
+ char buf[bufLen];
+ fprintf(outputFile, "allocation stack:\n");
+ for (size_t i = 0, length = record->allocationStack.size(); i < length;
+ ++i) {
+ gCodeAddressService->GetLocation(i, record->allocationStack[i], buf,
+ bufLen);
+ fprintf(outputFile, "%s\n", buf);
+ }
+ }
+
+ if (gLogJSStacks) {
+ if (record->jsStack) {
+ fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get());
+ } else {
+ fprintf(outputFile, "There is no JS context on the stack.\n");
+ }
+ }
+}
+
+template <>
+class nsDefaultComparator<BloatEntry*, BloatEntry*> {
+ public:
+ bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
+ return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0;
+ }
+ bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
+ return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0;
+ }
+};
+
+nsresult nsTraceRefcnt::DumpStatistics() {
+ if (!gBloatLog || !gBloatView) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoTraceLogLock lock(gTraceLog);
+
+ MOZ_ASSERT(!gDumpedStatistics,
+ "Calling DumpStatistics more than once may result in "
+ "bogus positive or negative leaks being reported");
+ gDumpedStatistics = true;
+
+ // Don't try to log while we hold the lock, we'd deadlock.
+ AutoRestore<LoggingType> saveLogging(gLogging);
+ gLogging = NoLogging;
+
+ BloatEntry total("TOTAL", 0);
+ for (const auto& data : gBloatView->Values()) {
+ if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) {
+ data->Total(&total);
+ }
+ }
+
+ const char* msg;
+ if (gLogLeaksOnly) {
+ msg = "ALL (cumulative) LEAK STATISTICS";
+ } else {
+ msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS";
+ }
+ const bool leaked = total.PrintDumpHeader(gBloatLog, msg);
+
+ nsTArray<BloatEntry*> entries(gBloatView->Count());
+ for (const auto& data : gBloatView->Values()) {
+ entries.AppendElement(data.get());
+ }
+
+ const uint32_t count = entries.Length();
+
+ if (!gLogLeaksOnly || leaked) {
+ // Sort the entries alphabetically by classname.
+ entries.Sort();
+
+ for (uint32_t i = 0; i < count; ++i) {
+ BloatEntry* entry = entries[i];
+ entry->Dump(i, gBloatLog);
+ }
+
+ fprintf(gBloatLog, "\n");
+ }
+
+ fprintf(gBloatLog, "nsTraceRefcnt::DumpStatistics: %d entries\n", count);
+
+ if (gSerialNumbers) {
+ bool onlyLoggingStringBuffers = gTypesToLog && gTypesToLog->Count() == 1 &&
+ gTypesToLog->Contains("nsStringBuffer");
+
+ fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n");
+ for (auto iter = gSerialNumbers->ConstIter(); !iter.Done(); iter.Next()) {
+ DumpSerialNumbers(iter, gBloatLog, onlyLoggingStringBuffers);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsTraceRefcnt::ResetStatistics() {
+ AutoTraceLogLock lock(gTraceLog);
+ gBloatView = nullptr;
+}
+
+static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) {
+ if (!aCreate) {
+ auto record = gSerialNumbers->Get(aPtr);
+ return record ? record->serialNumber : 0;
+ }
+
+ gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) {
+ if (entry) {
+ MOZ_CRASH(
+ "If an object already has a serial number, we should be destroying "
+ "it.");
+ }
+
+ auto& record = entry.Insert(mozilla::MakeUnique<SerialNumberRecord>());
+ WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC);
+ if (gLogJSStacks) {
+ record->SaveJSStack();
+ }
+ });
+ return gNextSerialNumber;
+}
+
+static void RecycleSerialNumberPtr(void* aPtr) { gSerialNumbers->Remove(aPtr); }
+
+static bool LogThisObj(intptr_t aSerialNumber) {
+ return gObjectsToLog->Contains(aSerialNumber);
+}
+
+using EnvCharType = mozilla::filesystem::Path::value_type;
+
+static bool InitLog(const EnvCharType* aEnvVar, const char* aMsg,
+ FILE** aResult, const char* aProcType) {
+#ifdef XP_WIN
+ // This is gross, I know.
+ const wchar_t* envvar = reinterpret_cast<const wchar_t*>(aEnvVar);
+ const char16_t* value = reinterpret_cast<const char16_t*>(::_wgetenv(envvar));
+# define ENVVAR_PRINTF "%S"
+#else
+ const char* envvar = aEnvVar;
+ const char* value = ::getenv(aEnvVar);
+# define ENVVAR_PRINTF "%s"
+#endif
+
+ if (value) {
+ nsTDependentString<EnvCharType> fname(value);
+ if (fname.EqualsLiteral("1")) {
+ *aResult = stdout;
+ fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n",
+ envvar, aMsg);
+ return true;
+ }
+ if (fname.EqualsLiteral("2")) {
+ *aResult = stderr;
+ fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n",
+ envvar, aMsg);
+ return true;
+ }
+ if (!XRE_IsParentProcess()) {
+ nsTString<EnvCharType> extension;
+ extension.AssignLiteral(".log");
+ bool hasLogExtension = StringEndsWith(fname, extension);
+ if (hasLogExtension) {
+ fname.Cut(fname.Length() - 4, 4);
+ }
+ fname.Append('_');
+ fname.AppendASCII(aProcType);
+ fname.AppendLiteral("_pid");
+ fname.AppendInt((uint32_t)getpid());
+ if (hasLogExtension) {
+ fname.AppendLiteral(".log");
+ }
+ }
+#ifdef XP_WIN
+ FILE* stream = ::_wfopen(fname.get(), L"wN");
+ const wchar_t* fp = (const wchar_t*)fname.get();
+#else
+ FILE* stream = ::fopen(fname.get(), "w");
+ const char* fp = fname.get();
+#endif
+ if (stream) {
+ MozillaRegisterDebugFD(fileno(stream));
+#ifdef MOZ_ENABLE_FORKSERVER
+ base::RegisterForkServerNoCloseFD(fileno(stream));
+#endif
+ *aResult = stream;
+ fprintf(stderr,
+ "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF
+ "\n",
+ envvar, aMsg, fp);
+
+ return true;
+ }
+
+ fprintf(stderr,
+ "### " ENVVAR_PRINTF
+ " defined -- unable to log %s to " ENVVAR_PRINTF "\n",
+ envvar, aMsg, fp);
+ MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
+
+#undef ENVVAR_PRINTF
+ }
+ return false;
+}
+
+static void maybeUnregisterAndCloseFile(FILE*& aFile) {
+ if (!aFile) {
+ return;
+ }
+
+ MozillaUnRegisterDebugFILE(aFile);
+ fclose(aFile);
+ aFile = nullptr;
+}
+
+static void DoInitTraceLog(const char* aProcType) {
+#ifdef XP_WIN
+# define ENVVAR(x) u"" x
+#else
+# define ENVVAR(x) x
+#endif
+
+ bool defined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
+ &gBloatLog, aProcType);
+ if (!defined) {
+ gLogLeaksOnly =
+ InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType);
+ }
+ if (defined || gLogLeaksOnly) {
+ // Use the same bloat view, if there is one, to keep it consistent
+ // between the fork server and content processes.
+ EnsureBloatView();
+ } else if (gBloatView) {
+ nsTraceRefcnt::ResetStatistics();
+ }
+
+ InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog, aProcType);
+
+ InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog, aProcType);
+
+ const char* classes = getenv("XPCOM_MEM_LOG_CLASSES");
+
+#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+ if (classes) {
+ InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType);
+ } else {
+ if (getenv("XPCOM_MEM_COMPTR_LOG")) {
+ fprintf(stdout,
+ "### XPCOM_MEM_COMPTR_LOG defined -- "
+ "but XPCOM_MEM_LOG_CLASSES is not defined\n");
+ }
+ }
+#else
+ const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG");
+ if (comptr_log) {
+ fprintf(stdout,
+ "### XPCOM_MEM_COMPTR_LOG defined -- "
+ "but it will not work without dynamic_cast\n");
+ }
+#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+
+#undef ENVVAR
+
+ if (classes) {
+ // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
+ // as a list of class names to track
+ //
+ // Use the same |gTypesToLog| and |gSerialNumbers| to keep them
+ // consistent through the fork server and content processes.
+ // Without this, counters will be incorrect.
+ if (!gTypesToLog) {
+ gTypesToLog = new CharPtrSet(256);
+ }
+
+ fprintf(stdout,
+ "### XPCOM_MEM_LOG_CLASSES defined -- "
+ "only logging these classes: ");
+ const char* cp = classes;
+ for (;;) {
+ char* cm = (char*)strchr(cp, ',');
+ if (cm) {
+ *cm = '\0';
+ }
+ if (!gTypesToLog->Contains(cp)) {
+ gTypesToLog->PutEntry(cp);
+ }
+ fprintf(stdout, "%s ", cp);
+ if (!cm) {
+ break;
+ }
+ *cm = ',';
+ cp = cm + 1;
+ }
+ fprintf(stdout, "\n");
+
+ if (!gSerialNumbers) {
+ gSerialNumbers = new SerialHash(256);
+ }
+ } else {
+ gTypesToLog = nullptr;
+ gSerialNumbers = nullptr;
+ }
+
+ const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS");
+ if (objects) {
+ gObjectsToLog = new IntPtrSet(256);
+
+ if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
+ fprintf(stdout,
+ "### XPCOM_MEM_LOG_OBJECTS defined -- "
+ "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
+ } else {
+ fprintf(stdout,
+ "### XPCOM_MEM_LOG_OBJECTS defined -- "
+ "only logging these objects: ");
+ const char* cp = objects;
+ for (;;) {
+ char* cm = (char*)strchr(cp, ',');
+ if (cm) {
+ *cm = '\0';
+ }
+ intptr_t top = 0;
+ intptr_t bottom = 0;
+ while (*cp) {
+ if (*cp == '-') {
+ bottom = top;
+ top = 0;
+ ++cp;
+ }
+ top *= 10;
+ top += *cp - '0';
+ ++cp;
+ }
+ if (!bottom) {
+ bottom = top;
+ }
+ for (intptr_t serialno = bottom; serialno <= top; serialno++) {
+ gObjectsToLog->PutEntry(serialno);
+ fprintf(stdout, "%" PRIdPTR " ", serialno);
+ }
+ if (!cm) {
+ break;
+ }
+ *cm = ',';
+ cp = cm + 1;
+ }
+ fprintf(stdout, "\n");
+ }
+ }
+
+ if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
+ fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n");
+ gLogJSStacks = true;
+ }
+
+ if (gBloatLog) {
+ gLogging = OnlyBloatLogging;
+ }
+
+ if (gRefcntsLog || gAllocLog || gCOMPtrLog) {
+ gLogging = FullLogging;
+ }
+}
+
+static void InitTraceLog() {
+ if (gInitialized) {
+ return;
+ }
+ gInitialized = true;
+
+ DoInitTraceLog(XRE_GetProcessTypeString());
+}
+
+extern "C" {
+
+static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) {
+#ifdef XP_WIN
+ int fd = _fileno(aStream);
+#else
+ int fd = fileno(aStream);
+#endif
+ while (aLen > 0) {
+#ifdef XP_WIN
+ auto written = _write(fd, aBuf, aLen);
+#else
+ auto written = write(fd, aBuf, aLen);
+#endif
+ if (written <= 0 || size_t(written) > aLen) {
+ break;
+ }
+ aBuf += written;
+ aLen -= written;
+ }
+}
+
+static void PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure) {
+ auto stream = static_cast<FILE*>(aClosure);
+ static const int buflen = 1024;
+ char buf[buflen + 5] = " "; // 5 for leading " " and trailing '\n'
+ int len =
+ gCodeAddressService->GetLocation(aFrameNumber, aPC, buf + 4, buflen);
+ len = std::min(len, buflen + 1 - 2) + 4;
+ buf[len++] = '\n';
+ buf[len] = '\0';
+ fflush(stream);
+ EnsureWrite(stream, buf, len);
+}
+
+static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC,
+ void* /*aSP*/, void* aClosure) {
+ auto locations = static_cast<std::vector<void*>*>(aClosure);
+ locations->push_back(aPC);
+}
+}
+
+/**
+ * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to
+ * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is
+ * being called frequently, it will be a few orders of magnitude faster than
+ * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause
+ * OOM crashes. Therefore, this should only be used for things like refcount
+ * logging which walk the stack extremely frequently.
+ */
+static void WalkTheStackCached(FILE* aStream, const void* aFirstFramePC) {
+ if (!gCodeAddressService) {
+ gCodeAddressService = new CodeAddressService<>();
+ }
+ MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0,
+ aStream);
+}
+
+static void WalkTheStackSavingLocations(std::vector<void*>& aLocations,
+ const void* aFirstFramePC) {
+ if (!gCodeAddressService) {
+ gCodeAddressService = new CodeAddressService<>();
+ }
+ MozStackWalk(RecordStackFrame, aFirstFramePC, /* maxFrames */ 0, &aLocations);
+}
+
+//----------------------------------------------------------------------
+
+EXPORT_XPCOM_API(void)
+NS_LogInit() {
+ NS_SetMainThread();
+
+ // FIXME: This is called multiple times, we should probably not allow that.
+ if (++gInitCount) {
+ nsTraceRefcnt::SetActivityIsLegal(true);
+ }
+}
+
+EXPORT_XPCOM_API(void)
+NS_LogTerm() { mozilla::LogTerm(); }
+
+#ifdef MOZ_DMD
+// If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
+// The value of this environment variable is used as the prefix
+// of the file name, so you probably want something like "/tmp/".
+// By default, this is run in all processes, but you can record a
+// log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
+// to the process type you want to log, such as "default" or "tab".
+// This method can't use the higher level XPCOM file utilities
+// because it is run very late in shutdown to avoid recording
+// information about refcount logging entries.
+static void LogDMDFile() {
+ const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
+ if (!dmdFilePrefix) {
+ return;
+ }
+
+ const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS");
+ if (logProcessEnv && !!strcmp(logProcessEnv, XRE_GetProcessTypeString())) {
+ return;
+ }
+
+ nsPrintfCString fileName("%sdmd-%" PRIPID ".log.gz", dmdFilePrefix,
+ base::GetCurrentProcId());
+ FILE* logFile = fopen(fileName.get(), "w");
+ if (NS_WARN_IF(!logFile)) {
+ return;
+ }
+
+ nsMemoryInfoDumper::DumpDMDToFile(logFile);
+}
+#endif // MOZ_DMD
+
+namespace mozilla {
+void LogTerm() {
+ NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit");
+
+ if (--gInitCount == 0) {
+#ifdef DEBUG
+ /* FIXME bug 491977: This is only going to operate on the
+ * BlockingResourceBase which is compiled into
+ * libxul/libxpcom_core.so. Anyone using external linkage will
+ * have their own copy of BlockingResourceBase statics which will
+ * not be freed by this method.
+ *
+ * It sounds like what we really want is to be able to register a
+ * callback function to call at XPCOM shutdown. Note that with
+ * this solution, however, we need to guarantee that
+ * BlockingResourceBase::Shutdown() runs after all other shutdown
+ * functions.
+ */
+ BlockingResourceBase::Shutdown();
+#endif
+
+ if (gInitialized) {
+ nsTraceRefcnt::DumpStatistics();
+ nsTraceRefcnt::ResetStatistics();
+ }
+ nsTraceRefcnt::Shutdown();
+ nsTraceRefcnt::SetActivityIsLegal(false);
+ gActivityTLS = BAD_TLS_INDEX;
+
+#ifdef MOZ_DMD
+ LogDMDFile();
+#endif
+ }
+}
+
+} // namespace mozilla
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass,
+ uint32_t aClassSize) {
+ ASSERT_ACTIVITY_IS_LEGAL(aClass, "addrefed");
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+ if (gLogging == NoLogging) {
+ return;
+ }
+ if (aRefcnt == 1 || gLogging == FullLogging) {
+ AutoTraceLogLock lock(gTraceLog);
+
+ if (aRefcnt == 1 && gBloatLog) {
+ BloatEntry* entry = GetBloatEntry(aClass, aClassSize);
+ if (entry) {
+ entry->Ctor();
+ }
+ }
+
+ // Here's the case where MOZ_COUNT_CTOR was not used,
+ // yet we still want to see creation information:
+
+ bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
+ intptr_t serialno = 0;
+ if (gSerialNumbers && loggingThisType) {
+ serialno = GetSerialNumber(aPtr, aRefcnt == 1, CallerPC());
+ MOZ_ASSERT(serialno != 0,
+ "Serial number requested for unrecognized pointer! "
+ "Are you memmoving a refcounted object?");
+ auto record = gSerialNumbers->Get(aPtr);
+ if (record) {
+ ++record->refCount;
+ }
+ }
+
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+ if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) {
+ fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Create [thread %p]\n", aClass,
+ aPtr, serialno, PR_GetCurrentThread());
+ WalkTheStackCached(gAllocLog, CallerPC());
+ }
+
+ if (gRefcntsLog && loggingThisType && loggingThisObject) {
+ // Can't use MOZ_LOG(), b/c it truncates the line
+ fprintf(gRefcntsLog,
+ "\n<%s> %p %" PRIuPTR " AddRef %" PRIuPTR " [thread %p]\n",
+ aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
+ WalkTheStackCached(gRefcntsLog, CallerPC());
+ fflush(gRefcntsLog);
+ }
+ }
+}
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) {
+ ASSERT_ACTIVITY_IS_LEGAL(aClass, "released");
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+ if (gLogging == NoLogging) {
+ return;
+ }
+ if (aRefcnt == 0 || gLogging == FullLogging) {
+ AutoTraceLogLock lock(gTraceLog);
+
+ if (aRefcnt == 0 && gBloatLog) {
+ BloatEntry* entry = GetBloatEntry(aClass, 0);
+ if (entry) {
+ entry->Dtor();
+ }
+ }
+
+ bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
+ intptr_t serialno = 0;
+ if (gSerialNumbers && loggingThisType) {
+ serialno = GetSerialNumber(aPtr, false, CallerPC());
+ MOZ_ASSERT(serialno != 0,
+ "Serial number requested for unrecognized pointer! "
+ "Are you memmoving a refcounted object?");
+ auto record = gSerialNumbers->Get(aPtr);
+ if (record) {
+ --record->refCount;
+ }
+ }
+
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+ if (gRefcntsLog && loggingThisType && loggingThisObject) {
+ // Can't use MOZ_LOG(), b/c it truncates the line
+ fprintf(gRefcntsLog,
+ "\n<%s> %p %" PRIuPTR " Release %" PRIuPTR " [thread %p]\n",
+ aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
+ WalkTheStackCached(gRefcntsLog, CallerPC());
+ fflush(gRefcntsLog);
+ }
+
+ // Here's the case where MOZ_COUNT_DTOR was not used,
+ // yet we still want to see deletion information:
+
+ if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) {
+ fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Destroy [thread %p]\n", aClass,
+ aPtr, serialno, PR_GetCurrentThread());
+ WalkTheStackCached(gAllocLog, CallerPC());
+ }
+
+ if (aRefcnt == 0 && gSerialNumbers && loggingThisType) {
+ RecycleSerialNumberPtr(aPtr);
+ }
+ }
+}
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
+ ASSERT_ACTIVITY_IS_LEGAL(aType, "constructed");
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+
+ if (gLogging == NoLogging) {
+ return;
+ }
+
+ AutoTraceLogLock lock(gTraceLog);
+
+ if (gBloatLog) {
+ BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
+ if (entry) {
+ entry->Ctor();
+ }
+ }
+
+ bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
+ intptr_t serialno = 0;
+ if (gSerialNumbers && loggingThisType) {
+ serialno = GetSerialNumber(aPtr, true, CallerPC());
+ MOZ_ASSERT(serialno != 0,
+ "GetSerialNumber should never return 0 when passed true");
+ }
+
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+ if (gAllocLog && loggingThisType && loggingThisObject) {
+ fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Ctor (%d)\n", aType, aPtr,
+ serialno, aInstanceSize);
+ WalkTheStackCached(gAllocLog, CallerPC());
+ }
+}
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
+ ASSERT_ACTIVITY_IS_LEGAL(aType, "destroyed");
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+
+ if (gLogging == NoLogging) {
+ return;
+ }
+
+ AutoTraceLogLock lock(gTraceLog);
+
+ if (gBloatLog) {
+ BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
+ if (entry) {
+ entry->Dtor();
+ }
+ }
+
+ bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
+ intptr_t serialno = 0;
+ if (gSerialNumbers && loggingThisType) {
+ serialno = GetSerialNumber(aPtr, false, CallerPC());
+ MOZ_ASSERT(serialno != 0,
+ "Serial number requested for unrecognized pointer! "
+ "Are you memmoving a MOZ_COUNT_CTOR-tracked object?");
+ RecycleSerialNumberPtr(aPtr);
+ }
+
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+
+ // (If we're on a losing architecture, don't do this because we'll be
+ // using LogDeleteXPCOM instead to get file and line numbers.)
+ if (gAllocLog && loggingThisType && loggingThisObject) {
+ fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", aType, aPtr,
+ serialno, aInstanceSize);
+ WalkTheStackCached(gAllocLog, CallerPC());
+ }
+}
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) {
+#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+ // Get the most-derived object.
+ void* object = dynamic_cast<void*>(aObject);
+
+ // This is a very indirect way of finding out what the class is
+ // of the object being logged. If we're logging a specific type,
+ // then
+ if (!gTypesToLog || !gSerialNumbers) {
+ return;
+ }
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+ if (gLogging == FullLogging) {
+ AutoTraceLogLock lock(gTraceLog);
+
+ intptr_t serialno = GetSerialNumber(object, false, CallerPC());
+ if (serialno == 0) {
+ return;
+ }
+
+ auto record = gSerialNumbers->Get(object);
+ int32_t count = record ? ++record->COMPtrCount : -1;
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+
+ if (gCOMPtrLog && loggingThisObject) {
+ fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n",
+ object, serialno, count, aCOMPtr);
+ WalkTheStackCached(gCOMPtrLog, CallerPC());
+ }
+ }
+#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+}
+
+EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
+NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) {
+#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+ // Get the most-derived object.
+ void* object = dynamic_cast<void*>(aObject);
+
+ // This is a very indirect way of finding out what the class is
+ // of the object being logged. If we're logging a specific type,
+ // then
+ if (!gTypesToLog || !gSerialNumbers) {
+ return;
+ }
+ if (!gInitialized) {
+ InitTraceLog();
+ }
+ if (gLogging == FullLogging) {
+ AutoTraceLogLock lock(gTraceLog);
+
+ intptr_t serialno = GetSerialNumber(object, false, CallerPC());
+ if (serialno == 0) {
+ return;
+ }
+
+ auto record = gSerialNumbers->Get(object);
+ int32_t count = record ? --record->COMPtrCount : -1;
+ bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
+
+ if (gCOMPtrLog && loggingThisObject) {
+ fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n",
+ object, serialno, count, aCOMPtr);
+ WalkTheStackCached(gCOMPtrLog, CallerPC());
+ }
+ }
+#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
+}
+
+static void ClearLogs(bool aKeepCounters) {
+ gCodeAddressService = nullptr;
+ // These counters from the fork server process will be preserved
+ // for the content processes to keep them consistent.
+ if (!aKeepCounters) {
+ gBloatView = nullptr;
+ gTypesToLog = nullptr;
+ gSerialNumbers = nullptr;
+ }
+ gObjectsToLog = nullptr;
+ gLogJSStacks = false;
+ gLogLeaksOnly = false;
+ maybeUnregisterAndCloseFile(gBloatLog);
+ maybeUnregisterAndCloseFile(gRefcntsLog);
+ maybeUnregisterAndCloseFile(gAllocLog);
+ maybeUnregisterAndCloseFile(gCOMPtrLog);
+}
+
+void nsTraceRefcnt::Shutdown() { ClearLogs(false); }
+
+void nsTraceRefcnt::SetActivityIsLegal(bool aLegal) {
+ if (gActivityTLS == BAD_TLS_INDEX) {
+ PR_NewThreadPrivateIndex(&gActivityTLS, nullptr);
+ }
+
+ PR_SetThreadPrivate(gActivityTLS, reinterpret_cast<void*>(!aLegal));
+}
+
+void nsTraceRefcnt::StartLoggingClass(const char* aClass) {
+ gLogging = FullLogging;
+
+ if (!gTypesToLog) {
+ gTypesToLog = new CharPtrSet(256);
+ }
+
+ fprintf(stdout, "### StartLoggingClass %s\n", aClass);
+ if (!gTypesToLog->Contains(aClass)) {
+ gTypesToLog->PutEntry(aClass);
+ }
+
+ // We are deliberately not initializing gSerialNumbers here, because
+ // it would cause assertions. gObjectsToLog can't be used because it
+ // relies on serial numbers.
+
+#ifdef XP_WIN
+# define ENVVAR(x) u"" x
+#else
+# define ENVVAR(x) x
+#endif
+
+ if (!gRefcntsLog) {
+ InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog,
+ XRE_GetProcessTypeString());
+ }
+
+#undef ENVVAR
+}
+
+#ifdef MOZ_ENABLE_FORKSERVER
+void nsTraceRefcnt::ResetLogFiles(const char* aProcType) {
+ AutoRestore<LoggingType> saveLogging(gLogging);
+ gLogging = NoLogging;
+
+ ClearLogs(true);
+
+ // Create log files with the correct process type in the name.
+ DoInitTraceLog(aProcType);
+}
+#endif
diff --git a/xpcom/base/nsTraceRefcnt.h b/xpcom/base/nsTraceRefcnt.h
new file mode 100644
index 0000000000..23eeec0bdf
--- /dev/null
+++ b/xpcom/base/nsTraceRefcnt.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTraceRefcnt_h
+#define nsTraceRefcnt_h
+
+#include "nscore.h"
+
+class nsTraceRefcnt {
+ public:
+ static void Shutdown();
+
+ static nsresult DumpStatistics();
+
+ static void ResetStatistics();
+
+ /**
+ * Tell nsTraceRefcnt whether refcounting, allocation, and destruction
+ * activity is legal. This is used to trigger assertions for any such
+ * activity that occurs because of static constructors or destructors.
+ */
+ static void SetActivityIsLegal(bool aLegal);
+
+ /**
+ * Start refcount logging aClass. If refcount logging has not already begun,
+ * it will use the environment variable XPCOM_MEM_LATE_REFCNT_LOG to decide
+ * where to make the log, in a similar way as the other nsTraceRefcnt logs.
+ */
+ static void StartLoggingClass(const char* aClass);
+
+#ifdef MOZ_ENABLE_FORKSERVER
+ static void ResetLogFiles(const char* aProcType = nullptr);
+#endif
+};
+
+#endif // nsTraceRefcnt_h
diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp
new file mode 100644
index 0000000000..f00437ada7
--- /dev/null
+++ b/xpcom/base/nsUUIDGenerator.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUUIDGenerator.h"
+
+NS_IMPL_ISUPPORTS(nsUUIDGenerator, nsIUUIDGenerator)
+
+nsUUIDGenerator::~nsUUIDGenerator() = default;
+
+NS_IMETHODIMP
+nsUUIDGenerator::GenerateUUID(nsID** aRet) {
+ nsID* id = static_cast<nsID*>(moz_xmalloc(sizeof(nsID)));
+
+ nsresult rv = GenerateUUIDInPlace(id);
+ if (NS_FAILED(rv)) {
+ free(id);
+ return rv;
+ }
+
+ *aRet = id;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) {
+ return nsID::GenerateUUIDInPlace(*aId);
+}
diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h
new file mode 100644
index 0000000000..b1b2921a3b
--- /dev/null
+++ b/xpcom/base/nsUUIDGenerator.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _NSUUIDGENERATOR_H_
+#define _NSUUIDGENERATOR_H_
+
+#include "nsIUUIDGenerator.h"
+
+class nsUUIDGenerator final : public nsIUUIDGenerator {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIUUIDGENERATOR
+
+ private:
+ ~nsUUIDGenerator();
+};
+
+#define NS_UUID_GENERATOR_CONTRACTID "@mozilla.org/uuid-generator;1"
+#define NS_UUID_GENERATOR_CID \
+ { \
+ 0x706d36bb, 0xbf79, 0x4293, { \
+ 0x81, 0xf2, 0x8f, 0x68, 0x28, 0xc1, 0x8f, 0x9d \
+ } \
+ }
+
+#endif /* _NSUUIDGENERATOR_H_ */
diff --git a/xpcom/base/nsVersionComparator.cpp b/xpcom/base/nsVersionComparator.cpp
new file mode 100644
index 0000000000..f12e54272a
--- /dev/null
+++ b/xpcom/base/nsVersionComparator.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVersionComparator.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include "mozilla/CheckedInt.h"
+#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL)
+# include <wchar.h>
+# include "nsString.h"
+#endif
+
+struct VersionPart {
+ int32_t numA;
+
+ const char* strB; // NOT null-terminated, can be a null pointer
+ uint32_t strBlen;
+
+ int32_t numC;
+
+ char* extraD; // null-terminated
+};
+
+#ifdef XP_WIN
+struct VersionPartW {
+ int32_t numA;
+
+ wchar_t* strB; // NOT null-terminated, can be a null pointer
+ uint32_t strBlen;
+
+ int32_t numC;
+
+ wchar_t* extraD; // null-terminated
+};
+#endif
+
+static int32_t ns_strtol(const char* aPart, char** aNext) {
+ errno = 0;
+ long result_long = strtol(aPart, aNext, 10);
+
+ // Different platforms seem to disagree on what to return when the value
+ // is out of range so we ensure that it is always what we want it to be.
+ // We choose 0 firstly because that is the default when the number doesn't
+ // exist at all and also because it would be easier to recover from should
+ // you somehow end up in a situation where an old version is invalid. It is
+ // much easier to create a version either larger or smaller than 0, much
+ // harder to do the same with INT_MAX.
+ if (errno != 0) {
+ return 0;
+ }
+
+ mozilla::CheckedInt<int32_t> result = result_long;
+ if (!result.isValid()) {
+ return 0;
+ }
+
+ return result.value();
+}
+
+/**
+ * Parse a version part into a number and "extra text".
+ *
+ * @returns A pointer to the next versionpart, or null if none.
+ */
+static char* ParseVP(char* aPart, VersionPart& aResult) {
+ char* dot;
+
+ aResult.numA = 0;
+ aResult.strB = nullptr;
+ aResult.strBlen = 0;
+ aResult.numC = 0;
+ aResult.extraD = nullptr;
+
+ if (!aPart) {
+ return aPart;
+ }
+
+ dot = strchr(aPart, '.');
+ if (dot) {
+ *dot = '\0';
+ }
+
+ if (aPart[0] == '*' && aPart[1] == '\0') {
+ aResult.numA = INT32_MAX;
+ aResult.strB = "";
+ } else {
+ aResult.numA = ns_strtol(aPart, const_cast<char**>(&aResult.strB));
+ }
+
+ if (!*aResult.strB) {
+ aResult.strB = nullptr;
+ aResult.strBlen = 0;
+ } else {
+ if (aResult.strB[0] == '+') {
+ static const char kPre[] = "pre";
+
+ ++aResult.numA;
+ aResult.strB = kPre;
+ aResult.strBlen = sizeof(kPre) - 1;
+ } else {
+ const char* numstart = strpbrk(aResult.strB, "0123456789+-");
+ if (!numstart) {
+ aResult.strBlen = strlen(aResult.strB);
+ } else {
+ aResult.strBlen = numstart - aResult.strB;
+ aResult.numC = ns_strtol(numstart, const_cast<char**>(&aResult.extraD));
+
+ if (!*aResult.extraD) {
+ aResult.extraD = nullptr;
+ }
+ }
+ }
+ }
+
+ if (dot) {
+ ++dot;
+
+ if (!*dot) {
+ dot = nullptr;
+ }
+ }
+
+ return dot;
+}
+
+/**
+ * Parse a version part into a number and "extra text".
+ *
+ * @returns A pointer to the next versionpart, or null if none.
+ */
+#ifdef XP_WIN
+
+static int32_t ns_wcstol(const wchar_t* aPart, wchar_t** aNext) {
+ errno = 0;
+ long result_long = wcstol(aPart, aNext, 10);
+
+ // See above for the rationale for using 0 here.
+ if (errno != 0) {
+ return 0;
+ }
+
+ mozilla::CheckedInt<int32_t> result = result_long;
+ if (!result.isValid()) {
+ return 0;
+ }
+
+ return result.value();
+}
+
+static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) {
+ wchar_t* dot;
+
+ aResult.numA = 0;
+ aResult.strB = nullptr;
+ aResult.strBlen = 0;
+ aResult.numC = 0;
+ aResult.extraD = nullptr;
+
+ if (!aPart) {
+ return aPart;
+ }
+
+ dot = wcschr(aPart, '.');
+ if (dot) {
+ *dot = '\0';
+ }
+
+ if (aPart[0] == '*' && aPart[1] == '\0') {
+ static wchar_t kEmpty[] = L"";
+
+ aResult.numA = INT32_MAX;
+ aResult.strB = kEmpty;
+ } else {
+ aResult.numA = ns_wcstol(aPart, const_cast<wchar_t**>(&aResult.strB));
+ }
+
+ if (!*aResult.strB) {
+ aResult.strB = nullptr;
+ aResult.strBlen = 0;
+ } else {
+ if (aResult.strB[0] == '+') {
+ static wchar_t kPre[] = L"pre";
+
+ ++aResult.numA;
+ aResult.strB = kPre;
+ aResult.strBlen = sizeof(kPre) - 1;
+ } else {
+ const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-");
+ if (!numstart) {
+ aResult.strBlen = wcslen(aResult.strB);
+ } else {
+ aResult.strBlen = numstart - aResult.strB;
+ aResult.numC =
+ ns_wcstol(numstart, const_cast<wchar_t**>(&aResult.extraD));
+
+ if (!*aResult.extraD) {
+ aResult.extraD = nullptr;
+ }
+ }
+ }
+ }
+
+ if (dot) {
+ ++dot;
+
+ if (!*dot) {
+ dot = nullptr;
+ }
+ }
+
+ return dot;
+}
+#endif
+
+// compare two null-terminated strings, which may be null pointers
+static int32_t ns_strcmp(const char* aStr1, const char* aStr2) {
+ // any string is *before* no string
+ if (!aStr1) {
+ return aStr2 != 0;
+ }
+
+ if (!aStr2) {
+ return -1;
+ }
+
+ return strcmp(aStr1, aStr2);
+}
+
+// compare two length-specified string, which may be null pointers
+static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2,
+ uint32_t aLen2) {
+ // any string is *before* no string
+ if (!aStr1) {
+ return aStr2 != 0;
+ }
+
+ if (!aStr2) {
+ return -1;
+ }
+
+ for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) {
+ if (*aStr1 < *aStr2) {
+ return -1;
+ }
+
+ if (*aStr1 > *aStr2) {
+ return 1;
+ }
+ }
+
+ if (aLen1 == 0) {
+ return aLen2 == 0 ? 0 : -1;
+ }
+
+ return 1;
+}
+
+// compare two int32_t
+static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) {
+ if (aNum1 < aNum2) {
+ return -1;
+ }
+
+ return aNum1 != aNum2;
+}
+
+/**
+ * Compares two VersionParts
+ */
+static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) {
+ int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
+ if (r) {
+ return r;
+ }
+
+ r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen);
+ if (r) {
+ return r;
+ }
+
+ r = ns_cmp(aVer1.numC, aVer2.numC);
+ if (r) {
+ return r;
+ }
+
+ return ns_strcmp(aVer1.extraD, aVer2.extraD);
+}
+
+/**
+ * Compares two VersionParts
+ */
+#ifdef XP_WIN
+static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) {
+ int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
+ if (r) {
+ return r;
+ }
+
+ r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen));
+ if (r) {
+ return r;
+ }
+
+ r = ns_cmp(aVer1.numC, aVer2.numC);
+ if (r) {
+ return r;
+ }
+
+ if (!aVer1.extraD) {
+ return aVer2.extraD != 0;
+ }
+
+ if (!aVer2.extraD) {
+ return -1;
+ }
+
+ return wcscmp(aVer1.extraD, aVer2.extraD);
+}
+#endif
+
+namespace mozilla {
+
+#ifdef XP_WIN
+int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) {
+ wchar_t* A2 = wcsdup(char16ptr_t(aStrA));
+ if (!A2) {
+ return 1;
+ }
+
+ wchar_t* B2 = wcsdup(char16ptr_t(aStrB));
+ if (!B2) {
+ free(A2);
+ return 1;
+ }
+
+ int32_t result;
+ wchar_t* a = A2;
+ wchar_t* b = B2;
+
+ do {
+ VersionPartW va, vb;
+
+ a = ParseVP(a, va);
+ b = ParseVP(b, vb);
+
+ result = CompareVP(va, vb);
+ if (result) {
+ break;
+ }
+
+ } while (a || b);
+
+ free(A2);
+ free(B2);
+
+ return result;
+}
+#endif
+
+int32_t CompareVersions(const char* aStrA, const char* aStrB) {
+ char* A2 = strdup(aStrA);
+ if (!A2) {
+ return 1;
+ }
+
+ char* B2 = strdup(aStrB);
+ if (!B2) {
+ free(A2);
+ return 1;
+ }
+
+ int32_t result;
+ char* a = A2;
+ char* b = B2;
+
+ do {
+ VersionPart va, vb;
+
+ a = ParseVP(a, va);
+ b = ParseVP(b, vb);
+
+ result = CompareVP(va, vb);
+ if (result) {
+ break;
+ }
+
+ } while (a || b);
+
+ free(A2);
+ free(B2);
+
+ return result;
+}
+
+} // namespace mozilla
diff --git a/xpcom/base/nsVersionComparator.h b/xpcom/base/nsVersionComparator.h
new file mode 100644
index 0000000000..32ea620583
--- /dev/null
+++ b/xpcom/base/nsVersionComparator.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// NB: This code may be used from non-XPCOM code, in particular, the
+// standalone updater executable.
+
+#ifndef nsVersionComparator_h__
+#define nsVersionComparator_h__
+
+#include "mozilla/Char16.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL)
+# include <wchar.h>
+# include "nsString.h"
+#endif
+
+/**
+ * In order to compare version numbers in Mozilla, you need to use the
+ * mozilla::Version class. You can construct an object of this type by passing
+ * in a string version number to the constructor. Objects of this type can be
+ * compared using the standard comparison operators.
+ *
+ * For example, let's say that you want to make sure that a given version
+ * number is not older than 15.a2. Here's how you would write a function to
+ * do that.
+ *
+ * bool IsVersionValid(const char* version) {
+ * return mozilla::Version("15.a2") <= mozilla::Version(version);
+ * }
+ *
+ * Or, since Version's constructor is implicit, you can simplify this code:
+ *
+ * bool IsVersionValid(const char* version) {
+ * return mozilla::Version("15.a2") <= version;
+ * }
+ */
+
+namespace mozilla {
+
+/**
+ * Compares the version strings provided.
+ *
+ * Returns 0 if the versions match, < 0 if aStrB > aStrA and > 0 if
+ * aStrA > aStrB.
+ */
+int32_t CompareVersions(const char* aStrA, const char* aStrB);
+
+#ifdef XP_WIN
+/**
+ * As above but for wide character strings.
+ */
+int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB);
+#endif
+
+struct Version {
+ explicit Version(const char* aVersionString) {
+ versionContent = strdup(aVersionString);
+ }
+
+ const char* ReadContent() const { return versionContent; }
+
+ ~Version() { free(versionContent); }
+
+ bool operator<(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) < 0;
+ }
+ bool operator<=(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) < 1;
+ }
+ bool operator>(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) > 0;
+ }
+ bool operator>=(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) > -1;
+ }
+ bool operator==(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) == 0;
+ }
+ bool operator!=(const Version& aRhs) const {
+ return CompareVersions(versionContent, aRhs.ReadContent()) != 0;
+ }
+ bool operator<(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) < 0;
+ }
+ bool operator<=(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) < 1;
+ }
+ bool operator>(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) > 0;
+ }
+ bool operator>=(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) > -1;
+ }
+ bool operator==(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) == 0;
+ }
+ bool operator!=(const char* aRhs) const {
+ return CompareVersions(versionContent, aRhs) != 0;
+ }
+
+ private:
+ char* versionContent;
+};
+
+} // namespace mozilla
+
+#endif // nsVersionComparator_h__
diff --git a/xpcom/base/nsVersionComparatorImpl.cpp b/xpcom/base/nsVersionComparatorImpl.cpp
new file mode 100644
index 0000000000..b52395bbac
--- /dev/null
+++ b/xpcom/base/nsVersionComparatorImpl.cpp
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVersionComparatorImpl.h"
+#include "nsVersionComparator.h"
+#include "nsString.h"
+
+NS_IMPL_ISUPPORTS(nsVersionComparatorImpl, nsIVersionComparator)
+
+NS_IMETHODIMP
+nsVersionComparatorImpl::Compare(const nsACString& aStr1,
+ const nsACString& aStr2, int32_t* aResult) {
+ *aResult = mozilla::CompareVersions(PromiseFlatCString(aStr1).get(),
+ PromiseFlatCString(aStr2).get());
+
+ return NS_OK;
+}
diff --git a/xpcom/base/nsVersionComparatorImpl.h b/xpcom/base/nsVersionComparatorImpl.h
new file mode 100644
index 0000000000..49b6f157a1
--- /dev/null
+++ b/xpcom/base/nsVersionComparatorImpl.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+
+#include "nsIVersionComparator.h"
+
+class nsVersionComparatorImpl final : public nsIVersionComparator {
+ ~nsVersionComparatorImpl() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIVERSIONCOMPARATOR
+};
+
+#define NS_VERSIONCOMPARATOR_CONTRACTID \
+ "@mozilla.org/xpcom/version-comparator;1"
+
+// c6e47036-ca94-4be3-963a-9abd8705f7a8
+#define NS_VERSIONCOMPARATOR_CID \
+ { \
+ 0xc6e47036, 0xca94, 0x4be3, { \
+ 0x96, 0x3a, 0x9a, 0xbd, 0x87, 0x05, 0xf7, 0xa8 \
+ } \
+ }
diff --git a/xpcom/base/nsWeakReference.cpp b/xpcom/base/nsWeakReference.cpp
new file mode 100644
index 0000000000..958ef08451
--- /dev/null
+++ b/xpcom/base/nsWeakReference.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// nsWeakReference.cpp
+
+#include "mozilla/Attributes.h"
+
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+
+class nsWeakReference final : public nsIWeakReference {
+ public:
+ // nsISupports...
+ NS_DECL_ISUPPORTS
+
+ // nsIWeakReference...
+ NS_DECL_NSIWEAKREFERENCE
+
+ private:
+ friend class nsSupportsWeakReference;
+
+ explicit nsWeakReference(nsSupportsWeakReference* aReferent)
+ : nsIWeakReference(aReferent)
+ // ...I can only be constructed by an |nsSupportsWeakReference|
+ {}
+
+ ~nsWeakReference()
+ // ...I will only be destroyed by calling |delete| myself.
+ {
+ MOZ_WEAKREF_ASSERT_OWNINGTHREAD;
+ if (mObject) {
+ static_cast<nsSupportsWeakReference*>(mObject)->NoticeProxyDestruction();
+ }
+ }
+
+ void NoticeReferentDestruction()
+ // ...called (only) by an |nsSupportsWeakReference| from _its_ dtor.
+ {
+ MOZ_WEAKREF_ASSERT_OWNINGTHREAD;
+ mObject = nullptr;
+ }
+};
+
+nsresult nsQueryReferent::operator()(const nsIID& aIID, void** aAnswer) const {
+ nsresult status;
+ if (mWeakPtr) {
+ if (NS_FAILED(status = mWeakPtr->QueryReferent(aIID, aAnswer))) {
+ *aAnswer = 0;
+ }
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ if (mErrorPtr) {
+ *mErrorPtr = status;
+ }
+ return status;
+}
+
+nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference* aInstancePtr,
+ nsresult* aErrorPtr) {
+ nsresult status;
+
+ nsIWeakReference* result = nullptr;
+
+ if (aInstancePtr) {
+ status = aInstancePtr->GetWeakReference(&result);
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ if (aErrorPtr) {
+ *aErrorPtr = status;
+ }
+
+ return result;
+}
+
+nsIWeakReference* // or else |already_AddRefed<nsIWeakReference>|
+NS_GetWeakReference(nsISupports* aInstancePtr, nsresult* aErrorPtr) {
+ nsresult status;
+
+ nsIWeakReference* result = nullptr;
+
+ if (aInstancePtr) {
+ nsCOMPtr<nsISupportsWeakReference> factoryPtr =
+ do_QueryInterface(aInstancePtr, &status);
+ if (factoryPtr) {
+ status = factoryPtr->GetWeakReference(&result);
+ }
+ // else, |status| has already been set by |do_QueryInterface|
+ } else {
+ status = NS_ERROR_NULL_POINTER;
+ }
+
+ if (aErrorPtr) {
+ *aErrorPtr = status;
+ }
+ return result;
+}
+
+nsresult nsSupportsWeakReference::GetWeakReference(
+ nsIWeakReference** aInstancePtr) {
+ if (!aInstancePtr) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mProxy) {
+ mProxy = new nsWeakReference(this);
+ } else {
+ MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(mProxy);
+ }
+ RefPtr<nsWeakReference> rval = mProxy;
+ rval.forget(aInstancePtr);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsWeakReference, nsIWeakReference)
+
+NS_IMETHODIMP
+nsWeakReference::QueryReferentFromScript(const nsIID& aIID,
+ void** aInstancePtr) {
+ return QueryReferent(aIID, aInstancePtr);
+}
+
+nsresult nsIWeakReference::QueryReferent(const nsIID& aIID,
+ void** aInstancePtr) {
+ MOZ_WEAKREF_ASSERT_OWNINGTHREAD;
+
+ if (!mObject) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return mObject->QueryInterface(aIID, aInstancePtr);
+}
+
+size_t nsWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this);
+}
+
+void nsSupportsWeakReference::ClearWeakReferences() {
+ if (mProxy) {
+ mProxy->NoticeReferentDestruction();
+ mProxy = nullptr;
+ }
+}
diff --git a/xpcom/base/nsWeakReference.h b/xpcom/base/nsWeakReference.h
new file mode 100644
index 0000000000..5303f2d2ea
--- /dev/null
+++ b/xpcom/base/nsWeakReference.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWeakReference_h__
+#define nsWeakReference_h__
+
+// nsWeakReference.h
+
+// See mfbt/WeakPtr.h for a more typesafe C++ implementation of weak references
+
+#include "nsIWeakReferenceUtils.h"
+
+class nsWeakReference;
+
+class nsSupportsWeakReference : public nsISupportsWeakReference {
+ public:
+ nsSupportsWeakReference() : mProxy(0) {}
+
+ NS_DECL_NSISUPPORTSWEAKREFERENCE
+
+ protected:
+ inline ~nsSupportsWeakReference();
+
+ private:
+ friend class nsWeakReference;
+
+ // Called (only) by an |nsWeakReference| from _its_ dtor.
+ // The thread safety check is made by the caller.
+ void NoticeProxyDestruction() { mProxy = nullptr; }
+
+ nsWeakReference* MOZ_NON_OWNING_REF mProxy;
+
+ protected:
+ void ClearWeakReferences();
+ bool HasWeakReferences() const { return !!mProxy; }
+};
+
+inline nsSupportsWeakReference::~nsSupportsWeakReference() {
+ ClearWeakReferences();
+}
+
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \
+ tmp->ClearWeakReferences();
+
+#define NS_IMPL_CYCLE_COLLECTION_WEAK(class_, ...) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_WEAK_INHERITED(class_, super_, ...) \
+ NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, super_) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, super_) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#endif
diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h
new file mode 100644
index 0000000000..7c91275803
--- /dev/null
+++ b/xpcom/base/nsWindowsHelpers.h
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// NB: This code may be used from non-XPCOM code, in particular, the
+// Windows Default Browser Agent.
+
+#ifndef nsWindowsHelpers_h
+#define nsWindowsHelpers_h
+
+#include <windows.h>
+#include <msi.h>
+#include "nsAutoRef.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/UniquePtr.h"
+
+// ----------------------------------------------------------------------------
+// Critical Section helper class
+// ----------------------------------------------------------------------------
+
+class AutoCriticalSection {
+ public:
+ explicit AutoCriticalSection(LPCRITICAL_SECTION aSection)
+ : mSection(aSection) {
+ ::EnterCriticalSection(mSection);
+ }
+ ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); }
+
+ private:
+ LPCRITICAL_SECTION mSection;
+};
+
+template <>
+class nsAutoRefTraits<HKEY> {
+ public:
+ typedef HKEY RawRef;
+ static HKEY Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ RegCloseKey(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<HDC> {
+ public:
+ typedef HDC RawRef;
+ static HDC Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ ::DeleteDC(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<HFONT> {
+ public:
+ typedef HFONT RawRef;
+ static HFONT Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ ::DeleteObject(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<HBRUSH> {
+ public:
+ typedef HBRUSH RawRef;
+ static HBRUSH Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ ::DeleteObject(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<HRGN> {
+ public:
+ typedef HRGN RawRef;
+ static HRGN Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ ::DeleteObject(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<HBITMAP> {
+ public:
+ typedef HBITMAP RawRef;
+ static HBITMAP Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ ::DeleteObject(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<SC_HANDLE> {
+ public:
+ typedef SC_HANDLE RawRef;
+ static SC_HANDLE Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ CloseServiceHandle(aFD);
+ }
+ }
+};
+
+template <>
+class nsSimpleRef<HANDLE> {
+ protected:
+ typedef HANDLE RawRef;
+
+ nsSimpleRef() : mRawRef(nullptr) {}
+
+ explicit nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) {}
+
+ bool HaveResource() const {
+ return mRawRef && mRawRef != INVALID_HANDLE_VALUE;
+ }
+
+ public:
+ RawRef get() const { return mRawRef; }
+
+ static void Release(RawRef aRawRef) {
+ if (aRawRef && aRawRef != INVALID_HANDLE_VALUE) {
+ CloseHandle(aRawRef);
+ }
+ }
+ RawRef mRawRef;
+};
+
+template <>
+class nsAutoRefTraits<HMODULE> {
+ public:
+ typedef HMODULE RawRef;
+ static RawRef Void() { return nullptr; }
+
+ static void Release(RawRef aFD) {
+ if (aFD != Void()) {
+ FreeLibrary(aFD);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<DEVMODEW*> {
+ public:
+ typedef DEVMODEW* RawRef;
+ static RawRef Void() { return nullptr; }
+
+ static void Release(RawRef aDevMode) {
+ if (aDevMode != Void()) {
+ ::HeapFree(::GetProcessHeap(), 0, aDevMode);
+ }
+ }
+};
+
+template <>
+class nsAutoRefTraits<MSIHANDLE> {
+ public:
+ typedef MSIHANDLE RawRef;
+ static RawRef Void() { return 0; }
+
+ static void Release(RawRef aHandle) {
+ if (aHandle != Void()) {
+ ::MsiCloseHandle(aHandle);
+ }
+ }
+};
+
+// HGLOBAL is just a typedef of HANDLE which nsSimpleRef has a specialization
+// of, that means having a nsAutoRefTraits specialization for HGLOBAL is
+// useless. Therefore we create a wrapper class for HGLOBAL to make
+// nsAutoRefTraits and nsAutoRef work as intention.
+class nsHGLOBAL {
+ public:
+ MOZ_IMPLICIT nsHGLOBAL(HGLOBAL hGlobal) : m_hGlobal(hGlobal) {}
+
+ operator HGLOBAL() const { return m_hGlobal; }
+
+ private:
+ HGLOBAL m_hGlobal;
+};
+
+template <>
+class nsAutoRefTraits<nsHGLOBAL> {
+ public:
+ typedef nsHGLOBAL RawRef;
+ static RawRef Void() { return nullptr; }
+
+ static void Release(RawRef hGlobal) { ::GlobalFree(hGlobal); }
+};
+
+// Because Printer's HANDLE uses ClosePrinter and we already have
+// nsAutoRef<HANDLE> which uses CloseHandle so we need to create a wrapper class
+// for HANDLE to have another specialization for nsAutoRefTraits.
+class nsHPRINTER {
+ public:
+ MOZ_IMPLICIT nsHPRINTER(HANDLE hPrinter) : m_hPrinter(hPrinter) {}
+
+ operator HANDLE() const { return m_hPrinter; }
+
+ HANDLE* operator&() { return &m_hPrinter; }
+
+ private:
+ HANDLE m_hPrinter;
+};
+
+// winspool.h header has AddMonitor macro, it conflicts with AddMonitor member
+// function in TaskbarPreview.cpp and TaskbarTabPreview.cpp. Beside, we only
+// need ClosePrinter here for Release function, so having its prototype is
+// enough.
+extern "C" BOOL WINAPI ClosePrinter(HANDLE hPrinter);
+
+template <>
+class nsAutoRefTraits<nsHPRINTER> {
+ public:
+ typedef nsHPRINTER RawRef;
+ static RawRef Void() { return nullptr; }
+
+ static void Release(RawRef hPrinter) { ::ClosePrinter(hPrinter); }
+};
+
+typedef nsAutoRef<HKEY> nsAutoRegKey;
+typedef nsAutoRef<HDC> nsAutoHDC;
+typedef nsAutoRef<HFONT> nsAutoFont;
+typedef nsAutoRef<HBRUSH> nsAutoBrush;
+typedef nsAutoRef<HRGN> nsAutoRegion;
+typedef nsAutoRef<HBITMAP> nsAutoBitmap;
+typedef nsAutoRef<SC_HANDLE> nsAutoServiceHandle;
+typedef nsAutoRef<HANDLE> nsAutoHandle;
+typedef nsAutoRef<HMODULE> nsModuleHandle;
+typedef nsAutoRef<DEVMODEW*> nsAutoDevMode;
+typedef nsAutoRef<nsHGLOBAL> nsAutoGlobalMem;
+typedef nsAutoRef<nsHPRINTER> nsAutoPrinter;
+typedef nsAutoRef<MSIHANDLE> nsAutoMsiHandle;
+
+// Construct a path "<system32>\<aModule>". return false if the output buffer
+// is too small.
+// Note: If the system path cannot be found, or doesn't fit in the output buffer
+// with the module name, we will just ignore the system path and output the
+// module name alone;
+// this may mean using a normal search path wherever the output is used.
+bool inline ConstructSystem32Path(LPCWSTR aModule, WCHAR* aSystemPath,
+ UINT aSize) {
+ MOZ_ASSERT(aSystemPath);
+
+ size_t fileLen = wcslen(aModule);
+ if (fileLen >= aSize) {
+ // The module name alone cannot even fit!
+ return false;
+ }
+
+ size_t systemDirLen = GetSystemDirectoryW(aSystemPath, aSize);
+
+ if (systemDirLen) {
+ if (systemDirLen < aSize - fileLen) {
+ // Make the system directory path terminate with a slash.
+ if (aSystemPath[systemDirLen - 1] != L'\\') {
+ if (systemDirLen + 1 < aSize - fileLen) {
+ aSystemPath[systemDirLen] = L'\\';
+ ++systemDirLen;
+ // No need to re-nullptr terminate.
+ } else {
+ // Couldn't fit the system path with added slash.
+ systemDirLen = 0;
+ }
+ }
+ } else {
+ // Couldn't fit the system path.
+ systemDirLen = 0;
+ }
+ }
+
+ MOZ_ASSERT(systemDirLen + fileLen < aSize);
+
+ wcsncpy(aSystemPath + systemDirLen, aModule, fileLen);
+ aSystemPath[systemDirLen + fileLen] = L'\0';
+ return true;
+}
+
+HMODULE inline LoadLibrarySystem32(LPCWSTR aModule) {
+ return LoadLibraryExW(aModule, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+}
+
+// for UniquePtr
+struct LocalFreeDeleter {
+ void operator()(void* aPtr) { ::LocalFree(aPtr); }
+};
+
+struct VirtualFreeDeleter {
+ void operator()(void* aPtr) { ::VirtualFree(aPtr, 0, MEM_RELEASE); }
+};
+
+// for UniquePtr to store a PSID
+struct FreeSidDeleter {
+ void operator()(void* aPtr) { ::FreeSid(aPtr); }
+};
+// Unfortunately, although SID is a struct, PSID is a void*
+// This typedef will work for storing a PSID in a UniquePtr and should make
+// things a bit more readable.
+typedef mozilla::UniquePtr<void, FreeSidDeleter> UniqueSidPtr;
+
+struct CloseHandleDeleter {
+ typedef HANDLE pointer;
+ void operator()(pointer aHandle) {
+ if (aHandle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(aHandle);
+ }
+ }
+};
+
+// One caller of this function is early in startup and several others are not,
+// so they have different ways of determining the two parameters. This function
+// exists just so any future code that needs to determine whether the dynamic
+// blocklist is disabled remembers to check whether safe mode is active.
+inline bool IsDynamicBlocklistDisabled(bool isSafeMode,
+ bool hasCommandLineDisableArgument) {
+ return isSafeMode || hasCommandLineDisableArgument;
+}
+#endif
diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h
new file mode 100644
index 0000000000..8881ddf5c8
--- /dev/null
+++ b/xpcom/base/nscore.h
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nscore_h___
+#define nscore_h___
+
+/* Definitions of functions and operators that allocate memory. */
+#if !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC)
+# include "mozilla/mozalloc.h"
+#endif
+
+/**
+ * Incorporate the integer data types which XPCOM uses.
+ */
+#include <stddef.h> // IWYU pragma: export
+#include <stdint.h> // IWYU pragma: export
+
+#include "mozilla/HelperMacros.h" // IWYU pragma: export
+#include "mozilla/RefCountType.h"
+
+/* Core XPCOM declarations. */
+
+/*----------------------------------------------------------------------*/
+/* Import/export defines */
+
+#ifdef HAVE_VISIBILITY_HIDDEN_ATTRIBUTE
+# define NS_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
+#else
+# define NS_VISIBILITY_HIDDEN
+#endif
+
+#if defined(HAVE_VISIBILITY_ATTRIBUTE)
+# define NS_VISIBILITY_DEFAULT __attribute__((visibility("default")))
+#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
+# define NS_VISIBILITY_DEFAULT __global
+#else
+# define NS_VISIBILITY_DEFAULT
+#endif
+
+#define NS_HIDDEN_(type) NS_VISIBILITY_HIDDEN type
+#define NS_EXTERNAL_VIS_(type) NS_VISIBILITY_DEFAULT type
+
+#define NS_HIDDEN NS_VISIBILITY_HIDDEN
+#define NS_EXTERNAL_VIS NS_VISIBILITY_DEFAULT
+
+/**
+ * Mark a function as using a potentially non-standard function calling
+ * convention. This can be used on functions that are called very
+ * frequently, to reduce the overhead of the function call. It is still worth
+ * using the macro for C++ functions which take no parameters since it allows
+ * passing |this| in a register.
+ *
+ * - Do not use this on any scriptable interface method since xptcall won't be
+ * aware of the different calling convention.
+ * - This must appear on the declaration, not the definition.
+ * - Adding this to a public function _will_ break binary compatibility.
+ * - This may be used on virtual functions but you must ensure it is applied
+ * to all implementations - the compiler will _not_ warn but it will crash.
+ * - This has no effect for functions which take a variable number of
+ * arguments.
+ * - __fastcall on windows should not be applied to class
+ * constructors/destructors - use the NS_CONSTRUCTOR_FASTCALL macro for
+ * constructors/destructors.
+ *
+ * Examples: int NS_FASTCALL func1(char *foo);
+ * NS_HIDDEN_(int) NS_FASTCALL func2(char *foo);
+ */
+
+#if defined(__i386__) && defined(__GNUC__)
+# define NS_FASTCALL __attribute__((regparm(3), stdcall))
+# define NS_CONSTRUCTOR_FASTCALL __attribute__((regparm(3), stdcall))
+#elif defined(XP_WIN) && !defined(_WIN64)
+# define NS_FASTCALL __fastcall
+# define NS_CONSTRUCTOR_FASTCALL
+#else
+# define NS_FASTCALL
+# define NS_CONSTRUCTOR_FASTCALL
+#endif
+
+/**
+ * Various API modifiers.
+ *
+ * - NS_IMETHOD/NS_IMETHOD_: use for in-class declarations and definitions.
+ * - NS_IMETHODIMP/NS_IMETHODIMP_: use for out-of-class definitions.
+ * - NS_METHOD_: usually used in conjunction with NS_CALLBACK_. Best avoided.
+ * - NS_CALLBACK_: used in some legacy situations. Best avoided.
+ */
+
+#ifdef XP_WIN
+
+# define NS_IMPORT __declspec(dllimport)
+# define NS_IMPORT_(type) __declspec(dllimport) type __stdcall
+# define NS_EXPORT __declspec(dllexport)
+# define NS_EXPORT_(type) __declspec(dllexport) type __stdcall
+# define NS_IMETHOD_(type) virtual type __stdcall
+# define NS_IMETHODIMP_(type) type __stdcall
+# define NS_METHOD_(type) type __stdcall
+# define NS_CALLBACK_(_type, _name) _type(__stdcall* _name)
+# ifndef _WIN64
+// Win64 has only one calling convention. __stdcall will be ignored by the
+// compiler.
+# define NS_STDCALL __stdcall
+# define NS_HAVE_STDCALL
+# else
+# define NS_STDCALL
+# endif
+# define NS_FROZENCALL __cdecl
+
+#else
+
+# define NS_IMPORT NS_EXTERNAL_VIS
+# define NS_IMPORT_(type) NS_EXTERNAL_VIS_(type)
+# define NS_EXPORT NS_EXTERNAL_VIS
+# define NS_EXPORT_(type) NS_EXTERNAL_VIS_(type)
+# define NS_IMETHOD_(type) virtual type
+# define NS_IMETHODIMP_(type) type
+# define NS_METHOD_(type) type
+# define NS_CALLBACK_(_type, _name) _type(*_name)
+# define NS_STDCALL
+# define NS_FROZENCALL
+
+#endif
+
+#define NS_IMETHOD NS_IMETHOD_(nsresult)
+#define NS_IMETHODIMP NS_IMETHODIMP_(nsresult)
+
+/**
+ * Import/Export macros for XPCOM APIs
+ */
+
+#define EXPORT_XPCOM_API(type) type
+#define IMPORT_XPCOM_API(type) type
+#define GLUE_XPCOM_API(type) type
+
+#ifdef __cplusplus
+# define NS_EXTERN_C extern "C"
+#else
+# define NS_EXTERN_C
+#endif
+
+#define XPCOM_API(type) NS_EXTERN_C type
+
+/* If a program allocates memory for the lifetime of the app, it doesn't make
+ * sense to touch memory pages and free that memory at shutdown,
+ * unless we are running leak stats.
+ *
+ * Note that we're also setting this for code coverage and pgo profile
+ * generation, because both of those require atexit hooks, which won't fire
+ * if we're using _exit. Bug 1555974 covers improving this.
+ *
+ */
+#ifndef NS_FREE_PERMANENT_DATA
+# if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND) || \
+ defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(MOZ_CODE_COVERAGE) || \
+ defined(MOZ_PROFILE_GENERATE) || defined(JS_STRUCTURED_SPEW)
+# define NS_FREE_PERMANENT_DATA
+# endif
+#endif
+
+/**
+ * NS_NO_VTABLE is emitted by xpidl in interface declarations whenever
+ * xpidl can determine that the interface can't contain a constructor.
+ * This results in some space savings and possible runtime savings -
+ * see bug 49416. We undefine it first, as xpidl-generated headers
+ * define it for IDL uses that don't include this file.
+ */
+#ifdef NS_NO_VTABLE
+# undef NS_NO_VTABLE
+#endif
+#if defined(_MSC_VER)
+# define NS_NO_VTABLE __declspec(novtable)
+#else
+# define NS_NO_VTABLE
+#endif
+
+/**
+ * Generic XPCOM result data type
+ */
+#include "nsError.h" // IWYU pragma: export
+
+typedef MozRefCountType nsrefcnt;
+
+namespace mozilla {
+// Extensions to the mozilla::Result type for handling of nsresult values.
+//
+// Note that these specializations need to be defined before Result.h is
+// included, or we run into explicit specialization after instantiation errors,
+// especially if Result.h is used in multiple sources in a unified compile.
+
+namespace detail {
+// When used as an error value, nsresult should never be NS_OK.
+// This specialization allows us to pack Result<Ok, nsresult> into a
+// nsresult-sized value.
+template <typename T>
+struct UnusedZero;
+template <>
+struct UnusedZero<nsresult> {
+ using StorageType = nsresult;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = NS_OK;
+
+ static constexpr void AssertValid(StorageType aValue) {}
+ static constexpr const nsresult& Inspect(const StorageType& aValue) {
+ return aValue;
+ }
+ static constexpr nsresult Unwrap(StorageType aValue) { return aValue; }
+ static constexpr StorageType Store(nsresult aValue) { return aValue; }
+};
+} // namespace detail
+
+template <typename T>
+class GenericErrorResult;
+template <>
+class GenericErrorResult<nsresult>;
+
+struct Ok;
+template <typename V, typename E>
+class Result;
+
+// Allow MOZ_TRY to handle `nsresult` values.
+template <typename E = nsresult>
+inline Result<Ok, E> ToResult(nsresult aValue);
+} // namespace mozilla
+
+/*
+ * Use these macros to do 64bit safe pointer conversions.
+ */
+
+#define NS_PTR_TO_INT32(x) ((int32_t)(intptr_t)(x))
+#define NS_PTR_TO_UINT32(x) ((uint32_t)(intptr_t)(x))
+#define NS_INT32_TO_PTR(x) ((void*)(intptr_t)(x))
+
+/*
+ * If we're being linked as standalone glue, we don't want a dynamic
+ * dependency on NSPR libs, so we skip the debug thread-safety
+ * checks, and we cannot use the THREADSAFE_ISUPPORTS macros.
+ */
+#if defined(XPCOM_GLUE) && !defined(XPCOM_GLUE_USE_NSPR)
+# define XPCOM_GLUE_AVOID_NSPR
+#endif
+
+/*
+ * SEH exception macros.
+ */
+#ifdef HAVE_SEH_EXCEPTIONS
+# define MOZ_SEH_TRY __try
+# define MOZ_SEH_EXCEPT(expr) __except (expr)
+#else
+# define MOZ_SEH_TRY if (true)
+# define MOZ_SEH_EXCEPT(expr) else
+#endif
+
+#endif /* nscore_h___ */
diff --git a/xpcom/base/nsrootidl.idl b/xpcom/base/nsrootidl.idl
new file mode 100644
index 0000000000..28e643e312
--- /dev/null
+++ b/xpcom/base/nsrootidl.idl
@@ -0,0 +1,105 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Root idl declarations to be used by all.
+ */
+
+%{C++
+
+#include "nscore.h"
+#include "nsID.h"
+typedef int64_t PRTime;
+
+/*
+ * Forward declarations for new string types
+ */
+#include "nsStringFwd.h"
+
+struct JSContext;
+
+/*
+ * Forward declaration of mozilla::dom::Promise
+ */
+namespace mozilla {
+namespace dom {
+class Promise;
+} // namespace dom
+} // namespace mozilla
+
+/*
+ * Start commenting out the C++ versions of the below in the output header
+ */
+#if 0
+%}
+
+typedef boolean bool ;
+typedef octet uint8_t ;
+typedef unsigned short uint16_t ;
+typedef unsigned short char16_t;
+typedef unsigned long uint32_t ;
+typedef unsigned long long uint64_t ;
+typedef long long PRTime ;
+typedef short int16_t ;
+typedef long int32_t ;
+typedef long long int64_t ;
+
+typedef unsigned long nsresult ;
+
+// If we ever want to use `size_t` in scriptable interfaces, this will need to
+// be built into the xpidl compiler, as the size varies based on platform.
+ native size_t(size_t);
+
+[ptr] native voidPtr(void);
+[ptr] native charPtr(char);
+[ptr] native unicharPtr(char16_t);
+
+[ref, nsid] native nsIDRef(nsID);
+[ref, nsid] native nsIIDRef(nsIID);
+[ref, nsid] native nsCIDRef(nsCID);
+
+[ptr, nsid] native nsIDPtr(nsID);
+[ptr, nsid] native nsIIDPtr(nsIID);
+[ptr, nsid] native nsCIDPtr(nsCID);
+
+// NOTE: Be careful in using the following 3 types. The *Ref and *Ptr variants
+// are more commonly used (and better supported). Those variants require
+// nsMemory alloc'd copies when used as 'out' params while these types do not.
+// However, currently these types can not be used for 'in' params. And, methods
+// that use them as 'out' params *must* be declared [notxpcom] (with an explicit
+// return type of nsresult). This makes such methods implicitly not scriptable.
+// Use of these types in methods without a [notxpcom] declaration will cause
+// the xpidl compiler to raise an error.
+// See: http://bugzilla.mozilla.org/show_bug.cgi?id=93792
+
+[nsid] native nsIID(nsIID);
+[nsid] native nsID(nsID);
+[nsid] native nsCID(nsCID);
+
+[ptr] native nsQIResult(void);
+
+[ref, utf8string] native AUTF8String(ignored);
+[ref, utf8string] native AUTF8StringRef(ignored);
+[ptr, utf8string] native AUTF8StringPtr(ignored);
+
+[ref, cstring] native ACString(ignored);
+[ref, cstring] native ACStringRef(ignored);
+[ptr, cstring] native ACStringPtr(ignored);
+
+[ref, astring] native AString(ignored);
+[ref, astring] native AStringRef(ignored);
+[ptr, astring] native AStringPtr(ignored);
+
+[ref, jsval] native jsval(jsval);
+ native jsid(jsid);
+
+[ptr, promise] native Promise(ignored);
+
+%{C++
+/*
+ * End commenting out the C++ versions of the above in the output header
+ */
+#endif
+%}