Bug 1348273 - Implementation of machine-generated crash annotations; r?ted.mielczarek draft
authorGabriele Svelto <gsvelto@mozilla.com>
Fri, 16 Mar 2018 21:04:39 +0100
changeset 781395 63e02f119b558fc775f21d53ebfc9fde2908400c
parent 779350 15678b283f0f62b7a27afba6e572ef8961d46c69
child 781396 f92a18f55e362250b213454c5a4440e5fb8ec280
push id106292
push usergsvelto@mozilla.com
push dateThu, 12 Apr 2018 22:06:23 +0000
reviewersted.mielczarek
bugs1348273
milestone61.0a1
Bug 1348273 - Implementation of machine-generated crash annotations; r?ted.mielczarek This patch introduces the machinery to generate crash annotations from a YAML file. The relevant functions are updated to take a typed enum (in C++) and an integer constant (in JavaScript). Once written out to the .extra file the annotations are converted in string form and are no different than the existing ones. The existing whitelists and blacklists of annotations are also generated from the YAML file and the existing duplicate code has been consolidated. MozReview-Commit-ID: 7bmnkxNphAN
ipc/glue/CrashReporterClient.cpp
ipc/glue/CrashReporterClient.h
ipc/glue/CrashReporterHost.cpp
ipc/glue/CrashReporterHost.h
ipc/glue/CrashReporterMetadataShmem.cpp
ipc/glue/CrashReporterMetadataShmem.h
layout/style/ServoBindings.cpp
mozglue/build/WindowsDllBlocklist.cpp
mozglue/build/WindowsDllBlocklist.h
servo/components/style/gecko/generated/bindings.rs
toolkit/components/crashes/CrashManager.jsm
toolkit/components/crashes/tests/xpcshell/crash.extra
toolkit/crashreporter/CrashAnnotations.cpp
toolkit/crashreporter/CrashAnnotations.h.in
toolkit/crashreporter/CrashAnnotations.yaml
toolkit/crashreporter/client/moz.build
toolkit/crashreporter/client/ping.cpp
toolkit/crashreporter/generate_crash_reporter_sources.py
toolkit/crashreporter/moz.build
toolkit/crashreporter/nsDummyExceptionHandler.cpp
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/crashreporter/nsExceptionHandler.h
toolkit/crashreporter/test/unit/test_crash_abort.js
toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js
toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js
toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js
toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js
toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js
toolkit/crashreporter/test/unit/test_crash_moz_crash.js
toolkit/crashreporter/test/unit/test_crash_oom.js
toolkit/crashreporter/test/unit/test_crash_purevirtual.js
toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
toolkit/crashreporter/test/unit/test_crashreporter.js
toolkit/crashreporter/test/unit/test_crashreporter_crash.js
toolkit/crashreporter/test/unit/test_event_files.js
toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
toolkit/xre/nsAppRunner.cpp
xpcom/system/moz.build
xpcom/system/nsICrashReporter.idl
xpcom/system/nsICrashReporter.idl.in
--- a/ipc/glue/CrashReporterClient.cpp
+++ b/ipc/glue/CrashReporterClient.cpp
@@ -21,17 +21,18 @@ CrashReporterClient::CrashReporterClient
 }
 
 CrashReporterClient::~CrashReporterClient()
 {
   MOZ_COUNT_DTOR(CrashReporterClient);
 }
 
 void
-CrashReporterClient::AnnotateCrashReport(const nsCString& aKey, const nsCString& aData)
+CrashReporterClient::AnnotateCrashReport(CrashReporter::Annotation aKey,
+                                         const nsCString& aData)
 {
   StaticMutexAutoLock lock(sLock);
   mMetadata->AnnotateCrashReport(aKey, aData);
 }
 
 void
 CrashReporterClient::AppendAppNotes(const nsCString& aData)
 {
--- a/ipc/glue/CrashReporterClient.h
+++ b/ipc/glue/CrashReporterClient.h
@@ -55,17 +55,18 @@ public:
       aOutShmem);
   }
 
   static void InitSingletonWithShmem(const Shmem& aShmem);
 
   static void DestroySingleton();
   static RefPtr<CrashReporterClient> GetSingleton();
 
-  void AnnotateCrashReport(const nsCString& aKey, const nsCString& aData);
+  void AnnotateCrashReport(CrashReporter::Annotation aKey,
+                           const nsCString& aData);
   void AppendAppNotes(const nsCString& aData);
 
 private:
   explicit CrashReporterClient(const Shmem& aShmem);
   ~CrashReporterClient();
 
 private:
   static StaticMutex sLock;
--- a/ipc/glue/CrashReporterHost.cpp
+++ b/ipc/glue/CrashReporterHost.cpp
@@ -59,17 +59,17 @@ CrashReporterHost::AdoptMinidump(nsIFile
 }
 
 bool
 CrashReporterHost::FinalizeCrashReport()
 {
   MOZ_ASSERT(!mFinalized);
   MOZ_ASSERT(HasMinidump());
 
-  CrashReporter::AnnotationTable notes;
+  CrashReporter::AnnotationTable annotations;
 
   nsAutoCString type;
   switch (mProcessType) {
     case GeckoProcessType_Content:
       type = NS_LITERAL_CSTRING("content");
       break;
     case GeckoProcessType_Plugin:
     case GeckoProcessType_GMPlugin:
@@ -77,32 +77,33 @@ CrashReporterHost::FinalizeCrashReport()
       break;
     case GeckoProcessType_GPU:
       type = NS_LITERAL_CSTRING("gpu");
       break;
     default:
       NS_ERROR("unknown process type");
       break;
   }
-  notes.Put(NS_LITERAL_CSTRING("ProcessType"), type);
+  annotations[CrashReporter::Annotation::ProcessType] = type;
 
   char startTime[32];
   SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime));
-  notes.Put(NS_LITERAL_CSTRING("StartupTime"), nsDependentCString(startTime));
+  annotations[CrashReporter::Annotation::StartupTime] =
+    nsDependentCString(startTime);
 
   // We might not have shmem (for example, when running crashreporter tests).
   if (mShmem.IsReadable()) {
-    CrashReporterMetadataShmem::ReadAppNotes(mShmem, &notes);
+    CrashReporterMetadataShmem::ReadAppNotes(mShmem, annotations);
   }
-  CrashReporter::AppendExtraData(mDumpID, mExtraNotes);
-  CrashReporter::AppendExtraData(mDumpID, notes);
+  CrashReporter::AppendExtraData(mDumpID, mExtraAnnotations);
+  CrashReporter::AppendExtraData(mDumpID, annotations);
 
-  // Use mExtraNotes, since NotifyCrashService looks for "PluginHang" which is
-  // set in the parent process.
-  NotifyCrashService(mProcessType, mDumpID, &mExtraNotes);
+  // Use mExtraAnnotations, since NotifyCrashService looks for "PluginHang"
+  // which is set in the parent process.
+  NotifyCrashService(mProcessType, mDumpID, mExtraAnnotations);
 
   mFinalized = true;
   return true;
 }
 
 namespace {
 class GenerateMinidumpShutdownBlocker : public nsIAsyncShutdownBlocker {
 public:
@@ -215,21 +216,21 @@ CrashReporterHost::GenerateMinidumpAndPa
                                         getter_AddRefs(mTargetDump),
                                         Move(callback),
                                         aAsync);
 }
 
 /* static */ void
 CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType,
                                       const nsString& aChildDumpID,
-                                      const AnnotationTable* aNotes)
+                                      const AnnotationTable& aNotes)
 {
   if (!NS_IsMainThread()) {
     RefPtr<Runnable> runnable = NS_NewRunnableFunction(
-      "ipc::CrashReporterHost::NotifyCrashService", [=]() -> void {
+      "ipc::CrashReporterHost::NotifyCrashService", [&]() -> void {
         CrashReporterHost::NotifyCrashService(
           aProcessType, aChildDumpID, aNotes);
       });
     RefPtr<nsIThread> mainThread = do_GetMainThread();
     SyncRunnable::DispatchToThread(mainThread, runnable);
     return;
   }
 
@@ -249,19 +250,18 @@ CrashReporterHost::NotifyCrashService(Ge
   switch (aProcessType) {
     case GeckoProcessType_Content:
       processType = nsICrashService::PROCESS_TYPE_CONTENT;
       telemetryKey.AssignLiteral("content");
       break;
     case GeckoProcessType_Plugin: {
       processType = nsICrashService::PROCESS_TYPE_PLUGIN;
       telemetryKey.AssignLiteral("plugin");
-      nsAutoCString val;
-      if (aNotes->Get(NS_LITERAL_CSTRING("PluginHang"), &val) &&
-        val.EqualsLiteral("1")) {
+      nsCString val = aNotes[CrashReporter::Annotation::PluginHang];
+      if (val.Equals(NS_LITERAL_CSTRING("1"))) {
         crashType = nsICrashService::CRASH_TYPE_HANG;
         telemetryKey.AssignLiteral("pluginhang");
       }
       break;
     }
     case GeckoProcessType_GMPlugin:
       processType = nsICrashService::PROCESS_TYPE_GMPLUGIN;
       telemetryKey.AssignLiteral("gmplugin");
@@ -276,15 +276,41 @@ CrashReporterHost::NotifyCrashService(Ge
   }
 
   nsCOMPtr<nsISupports> promise;
   crashService->AddCrash(processType, crashType, aChildDumpID, getter_AddRefs(promise));
   Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey, 1);
 }
 
 void
-CrashReporterHost::AddNote(const nsCString& aKey, const nsCString& aValue)
+CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey, bool aValue)
+{
+  mExtraAnnotations[aKey] = aValue ? NS_LITERAL_CSTRING("1")
+                                   : NS_LITERAL_CSTRING("0");
+}
+
+void
+CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+                                 int aValue)
 {
-  mExtraNotes.Put(aKey, aValue);
+  nsAutoCString valueString;
+  valueString.AppendInt(aValue);
+  mExtraAnnotations[aKey] = valueString;
+}
+
+void
+CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+                                 unsigned int aValue)
+{
+  nsAutoCString valueString;
+  valueString.AppendInt(aValue);
+  mExtraAnnotations[aKey] = valueString;
+}
+
+void
+CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
+                                 const nsCString& aValue)
+{
+  mExtraAnnotations[aKey] = aValue;
 }
 
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/glue/CrashReporterHost.h
+++ b/ipc/glue/CrashReporterHost.h
@@ -125,19 +125,22 @@ public:
   // This is a static helper function to notify the crash service that a
   // crash has occurred. When PCrashReporter is removed, we can make this
   // a member function. This can be called from any thread, and if not
   // called from the main thread, will post a synchronous message to the
   // main thread.
   static void NotifyCrashService(
     GeckoProcessType aProcessType,
     const nsString& aChildDumpID,
-    const AnnotationTable* aNotes);
+    const AnnotationTable& aNotes);
 
-  void AddNote(const nsCString& aKey, const nsCString& aValue);
+  void AddAnnotation(CrashReporter::Annotation aKey, bool aValue);
+  void AddAnnotation(CrashReporter::Annotation aKey, int aValue);
+  void AddAnnotation(CrashReporter::Annotation aKey, unsigned int aValue);
+  void AddAnnotation(CrashReporter::Annotation aKey, const nsCString& aValue);
 
   bool HasMinidump() const {
     return !mDumpID.IsEmpty();
   }
   const nsString& MinidumpID() const {
     MOZ_ASSERT(HasMinidump());
     return mDumpID;
   }
@@ -147,17 +150,17 @@ private:
                             const nsString& aChildDumpID);
 
 private:
   CallbackWrapper<bool> mCreateMinidumpCallback;
   GeckoProcessType mProcessType;
   Shmem mShmem;
   ThreadId mThreadId;
   time_t mStartTime;
-  AnnotationTable mExtraNotes;
+  AnnotationTable mExtraAnnotations;
   nsString mDumpID;
   bool mFinalized;
   nsCOMPtr<nsIFile> mTargetDump;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
--- a/ipc/glue/CrashReporterMetadataShmem.cpp
+++ b/ipc/glue/CrashReporterMetadataShmem.cpp
@@ -1,21 +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 "CrashReporterMetadataShmem.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/EnumeratedRange.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace ipc {
 
+using CrashReporter::Annotation;
+
 enum class EntryType : uint8_t {
   None,
   Annotation,
 };
 
 CrashReporterMetadataShmem::CrashReporterMetadataShmem(const Shmem& aShmem)
  : mShmem(aShmem)
 {
@@ -23,41 +26,42 @@ CrashReporterMetadataShmem::CrashReporte
 }
 
 CrashReporterMetadataShmem::~CrashReporterMetadataShmem()
 {
   MOZ_COUNT_DTOR(CrashReporterMetadataShmem);
 }
 
 void
-CrashReporterMetadataShmem::AnnotateCrashReport(const nsCString& aKey, const nsCString& aData)
+CrashReporterMetadataShmem::AnnotateCrashReport(Annotation aKey,
+                                                const nsCString& aData)
 {
-  mNotes.Put(aKey, aData);
+  mAnnotations[aKey] = aData;
   SyncNotesToShmem();
 }
 
 void
 CrashReporterMetadataShmem::AppendAppNotes(const nsCString& aData)
 {
   mAppNotes.Append(aData);
-  mNotes.Put(NS_LITERAL_CSTRING("Notes"), mAppNotes);
+  mAnnotations[Annotation::Notes] = mAppNotes;
   SyncNotesToShmem();
 }
 
 class MOZ_STACK_CLASS MetadataShmemWriter
 {
 public:
   explicit MetadataShmemWriter(const Shmem& aShmem)
    : mCursor(aShmem.get<uint8_t>()),
      mEnd(mCursor + aShmem.Size<uint8_t>())
   {
     *mCursor = uint8_t(EntryType::None);
   }
 
-  MOZ_MUST_USE bool WriteAnnotation(const nsCString& aKey, const nsCString& aValue) {
+  MOZ_MUST_USE bool WriteAnnotation(Annotation aKey, const nsCString& aValue) {
     // This shouldn't happen because Commit() guarantees mCursor < mEnd. But
     // we might as well be safe.
     if (mCursor >= mEnd) {
       return false;
     }
 
     // Save the current position so we can write the entry type if the entire
     // entry fits.
@@ -120,21 +124,21 @@ private:
   uint8_t* mEnd;
 };
 
 void
 CrashReporterMetadataShmem::SyncNotesToShmem()
 {
   MetadataShmemWriter writer(mShmem);
 
-  for (auto it = mNotes.Iter(); !it.Done(); it.Next()) {
-    nsCString key = nsCString(it.Key());
-    nsCString value = nsCString(it.Data());
-    if (!writer.WriteAnnotation(key, value)) {
-      return;
+  for (auto key : MakeEnumeratedRange(Annotation::Count)) {
+    if (!mAnnotations[key].IsEmpty()) {
+      if (!writer.WriteAnnotation(key, mAnnotations[key])) {
+        return;
+      }
     }
   }
 }
 
 // Helper class to iterate over metadata entries encoded in shmem.
 class MOZ_STACK_CLASS MetadataShmemReader
 {
 public:
@@ -157,36 +161,37 @@ public:
   void Next() {
     if (mCursor < mEnd) {
       mEntryType = EntryType(*mCursor++);
     } else {
       mEntryType = EntryType::None;
     }
   }
 
+  template <typename T>
+  bool Read(T* aOut) {
+    return Read(aOut, sizeof(T));
+  }
+
   bool Read(nsCString& aOut) {
     uint32_t length = 0;
     if (!Read(&length)) {
       return false;
     }
 
     const uint8_t* src = Read(length);
     if (!src) {
       return false;
     }
 
     aOut.Assign((const char *)src, length);
     return true;
   }
 
 private:
-  template <typename T>
-  bool Read(T* aOut) {
-    return Read(aOut, sizeof(T));
-  }
   bool Read(void* aOut, size_t aLength) {
     const uint8_t* src = Read(aLength);
     if (!src) {
       return false;
     }
     memcpy(aOut, src, aLength);
     return true;
   }
@@ -204,27 +209,29 @@ private:
 
 private:
   const uint8_t* mCursor;
   const uint8_t* mEnd;
   EntryType mEntryType;
 };
 
 void
-CrashReporterMetadataShmem::ReadAppNotes(const Shmem& aShmem, CrashReporter::AnnotationTable* aNotes)
+CrashReporterMetadataShmem::ReadAppNotes(const Shmem& aShmem,
+                                         AnnotationTable& aNotes)
 {
   for (MetadataShmemReader reader(aShmem); !reader.Done(); reader.Next()) {
     switch (reader.Type()) {
       case EntryType::Annotation: {
-        nsCString key, value;
-        if (!reader.Read(key) || !reader.Read(value)) {
+        Annotation key;
+        nsCString value;
+        if (!reader.Read(&key) || !reader.Read(value)) {
           return;
         }
 
-        aNotes->Put(key, value);
+        aNotes[key] = value;
         break;
       }
       default:
         NS_ASSERTION(false, "Unknown metadata entry type");
         break;
     }
   }
 }
--- a/ipc/glue/CrashReporterMetadataShmem.h
+++ b/ipc/glue/CrashReporterMetadataShmem.h
@@ -20,27 +20,29 @@ class CrashReporterMetadataShmem
   typedef mozilla::ipc::Shmem Shmem;
   typedef CrashReporter::AnnotationTable AnnotationTable;
 
 public:
   explicit CrashReporterMetadataShmem(const Shmem& aShmem);
   ~CrashReporterMetadataShmem();
 
   // Metadata writers. These must only be called in child processes.
-  void AnnotateCrashReport(const nsCString& aKey, const nsCString& aData);
+  void AnnotateCrashReport(CrashReporter::Annotation aKey,
+                           const nsCString& aData);
   void AppendAppNotes(const nsCString& aData);
 
-  static void ReadAppNotes(const Shmem& aShmem, CrashReporter::AnnotationTable* aNotes);
+  static void ReadAppNotes(const Shmem& aShmem,
+                           CrashReporter::AnnotationTable& aNotes);
 
 private:
   void SyncNotesToShmem();
 
 private:
   Shmem mShmem;
 
-  AnnotationTable mNotes;
+  AnnotationTable mAnnotations;
   nsCString mAppNotes;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_CrashReporterMetadataShmem_h
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2713,20 +2713,19 @@ Gecko_AddBufferToCrashReport(const void*
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsICrashReporter> cr = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
   NS_ENSURE_TRUE_VOID(cr);
   cr->RegisterAppMemory((uint64_t) addr, len);
 }
 
 void
-Gecko_AnnotateCrashReport(const char* key_str, const char* value_str)
+Gecko_AnnotateCrashReport(uint32_t key, const char* value_str)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  nsDependentCString key(key_str);
   nsDependentCString value(value_str);
   nsCOMPtr<nsICrashReporter> cr = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
   NS_ENSURE_TRUE_VOID(cr);
   cr->AnnotateCrashReport(key, value);
 }
 
 void
 Gecko_ContentList_AppendAll(
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -277,28 +277,16 @@ static const DllBlockInfo sWindowsDllBlo
 
 #ifndef STATUS_DLL_NOT_FOUND
 #define STATUS_DLL_NOT_FOUND ((DWORD)0xC0000135L)
 #endif
 
 // define this for very verbose dll load debug spew
 #undef DEBUG_very_verbose
 
-static const char kBlockedDllsParameter[] = "BlockedDllList=";
-static const int kBlockedDllsParameterLen =
-  sizeof(kBlockedDllsParameter) - 1;
-
-static const char kBlocklistInitFailedParameter[] = "BlocklistInitFailed=1\n";
-static const int kBlocklistInitFailedParameterLen =
-  sizeof(kBlocklistInitFailedParameter) - 1;
-
-static const char kUser32BeforeBlocklistParameter[] = "User32BeforeBlocklist=1\n";
-static const int kUser32BeforeBlocklistParameterLen =
-  sizeof(kUser32BeforeBlocklistParameter) - 1;
-
 static uint32_t sInitFlags;
 static bool sBlocklistInitAttempted;
 static bool sBlocklistInitFailed;
 static bool sUser32BeforeBlocklist;
 
 // Duplicated from xpcom glue. Ideally this should be shared.
 void
 printf_stderr(const char *fmt, ...)
@@ -953,32 +941,38 @@ DllBlocklist_Initialize(uint32_t aInitFl
     if (pProc) {
       gStartAddressesToBlock->append(pProc);
     }
   }
 #endif
 }
 
 MFBT_API void
-DllBlocklist_WriteNotes(HANDLE file)
+DllBlocklist_WriteNotes(HANDLE file, const char* aBlockedDllListAnnotation,
+                        const char* aBlocklistInitFailedAnnotation,
+                        const char* aUser32BeforeBlocklistAnnotation)
 {
   DWORD nBytes;
 
-  WriteFile(file, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr);
+  WriteFile(file, aBlockedDllListAnnotation, strlen(aBlockedDllListAnnotation),
+            &nBytes, nullptr);
+  WriteFile(file, "=", 1, &nBytes, nullptr);
   DllBlockSet::Write(file);
   WriteFile(file, "\n", 1, &nBytes, nullptr);
 
   if (sBlocklistInitFailed) {
-    WriteFile(file, kBlocklistInitFailedParameter,
-              kBlocklistInitFailedParameterLen, &nBytes, nullptr);
+    WriteFile(file, aBlocklistInitFailedAnnotation,
+              strlen(aBlocklistInitFailedAnnotation), &nBytes, nullptr);
+    WriteFile(file, "=1", 2, &nBytes, nullptr);
   }
 
   if (sUser32BeforeBlocklist) {
-    WriteFile(file, kUser32BeforeBlocklistParameter,
-              kUser32BeforeBlocklistParameterLen, &nBytes, nullptr);
+    WriteFile(file, aUser32BeforeBlocklistAnnotation,
+              strlen(aUser32BeforeBlocklistAnnotation), &nBytes, nullptr);
+    WriteFile(file, "=1", 2, &nBytes, nullptr);
   }
 }
 
 MFBT_API bool
 DllBlocklist_CheckStatus()
 {
   if (sBlocklistInitFailed || sUser32BeforeBlocklist)
     return false;
--- a/mozglue/build/WindowsDllBlocklist.h
+++ b/mozglue/build/WindowsDllBlocklist.h
@@ -16,17 +16,20 @@
 
 enum DllBlocklistInitFlags
 {
   eDllBlocklistInitFlagDefault = 0,
   eDllBlocklistInitFlagIsChildProcess = 1
 };
 
 MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
-MFBT_API void DllBlocklist_WriteNotes(HANDLE file);
+MFBT_API void DllBlocklist_WriteNotes(HANDLE file,
+                                      const char* aBlockedDllListAnnotation,
+                                      const char* aBlocklistInitFailedAnnotation,
+                                      const char* aUser32BeforeBlocklistAnnotation);
 MFBT_API bool DllBlocklist_CheckStatus();
 
 // Forward declaration
 namespace mozilla {
 namespace glue {
 namespace detail {
 class DllServicesBase;
 } // namespace detail
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1942,17 +1942,17 @@ extern "C" {
 extern "C" {
     pub fn Gecko_SetJemallocThreadLocalArena(enabled: bool);
 }
 extern "C" {
     pub fn Gecko_AddBufferToCrashReport(addr: *const ::std::os::raw::c_void, len: usize);
 }
 extern "C" {
     pub fn Gecko_AnnotateCrashReport(
-        key_str: *const ::std::os::raw::c_char,
+        key: u32,
         value_str: *const ::std::os::raw::c_char,
     );
 }
 extern "C" {
     pub fn Servo_Element_ClearData(node: RawGeckoElementBorrowed);
 }
 extern "C" {
     pub fn Servo_Element_SizeOfExcludingThisAndCVs(
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -216,53 +216,16 @@ this.CrashManager.prototype = Object.fre
   // The following are return codes for individual event file processing.
   // File processed OK.
   EVENT_FILE_SUCCESS: "ok",
   // The event appears to be malformed.
   EVENT_FILE_ERROR_MALFORMED: "malformed",
   // The type of event is unknown.
   EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
 
-  // A whitelist of crash annotations which do not contain sensitive data
-  // and are saved in the crash record and sent with Firefox Health Report.
-  ANNOTATION_WHITELIST: [
-    "AsyncShutdownTimeout",
-    "BuildID",
-    "ipc_channel_error",
-    "ProductID",
-    "ProductName",
-    "ReleaseChannel",
-    "RemoteType",
-    "SecondsSinceLastCrash",
-    "ShutdownProgress",
-    "StartupCrash",
-    "TelemetryEnvironment",
-    "Version",
-    // The following entries are not normal annotations that can be found in
-    // the .extra file but are included in the crash record/FHR:
-    "AvailablePageFile",
-    "AvailablePhysicalMemory",
-    "AvailableVirtualMemory",
-    "BlockedDllList",
-    "BlocklistInitFailed",
-    "ContainsMemoryReport",
-    "CrashTime",
-    "EventLoopNestingLevel",
-    "IsGarbageCollecting",
-    "MozCrashReason",
-    "OOMAllocationSize",
-    "SystemMemoryUsePercentage",
-    "TextureUsage",
-    "TotalPageFile",
-    "TotalPhysicalMemory",
-    "TotalVirtualMemory",
-    "UptimeTS",
-    "User32BeforeBlocklist",
-  ],
-
   /**
    * Obtain a list of all dumps pending upload.
    *
    * The returned value is a promise that resolves to an array of objects
    * on success. Each element in the array has the following properties:
    *
    *   id (string)
    *      The ID of the crash (a UUID).
@@ -631,20 +594,26 @@ this.CrashManager.prototype = Object.fre
       let payload = data.substring(start);
 
       return this._handleEventFilePayload(store, entry, type, date, payload);
     })();
   },
 
   _filterAnnotations(annotations) {
     let filteredAnnotations = {};
+    let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+                          .getService(Ci.nsICrashReporter);
 
     for (let line in annotations) {
-      if (this.ANNOTATION_WHITELIST.includes(line)) {
-        filteredAnnotations[line] = annotations[line];
+      try {
+        if (crashReporter.isAnnotationWhitelistedForPing(line)) {
+          filteredAnnotations[line] = annotations[line];
+        }
+      } catch (e) {
+        // Silently drop unknown annotations
       }
     }
 
     return filteredAnnotations;
   },
 
   _sendCrashPing(crashId, type, date, metadata = {}) {
     // If we have a saved environment, use it. Otherwise report
--- a/toolkit/components/crashes/tests/xpcshell/crash.extra
+++ b/toolkit/components/crashes/tests/xpcshell/crash.extra
@@ -3,17 +3,16 @@ TelemetryEnvironment={"EscapedField":"Es
 EMCheckCompatibility=true
 ProductName=Firefox
 ContentSandboxCapabilities=119
 TelemetryClientId=
 Vendor=Mozilla
 InstallTime=1000000000
 Theme=classic/1.0
 ReleaseChannel=default
-AddonsShouldHaveBlockedE10s=1
 ServerURL=https://crash-reports.mozilla.com
 SafeMode=0
 ContentSandboxCapable=1
 useragent_locale=en-US
 Version=55.0a1
 BuildID=20170512114708
 ProductID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
 TelemetryServerURL=
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/CrashAnnotations.cpp
@@ -0,0 +1,63 @@
+/* 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 "CrashAnnotations.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+
+using std::begin;
+using std::end;
+using std::find_if;
+
+namespace CrashReporter {
+
+bool
+AnnotationFromString(Annotation& aResult, const char* aValue)
+{
+  auto elem = find_if(
+    begin(kAnnotationStrings),
+    end(kAnnotationStrings),
+    [&aValue](const char* aString) {
+      return strcmp(aString, aValue) == 0;
+    }
+  );
+
+  if (elem == end(kAnnotationStrings)) {
+    return false;
+  }
+
+  aResult = static_cast<Annotation>(elem - begin(kAnnotationStrings));
+  return true;
+}
+
+bool
+IsAnnotationWhitelistedForPing(Annotation aAnnotation) {
+  auto elem = find_if(
+    begin(kCrashPingWhitelist),
+    end(kCrashPingWhitelist),
+    [&aAnnotation](Annotation aElement) {
+      return aElement == aAnnotation;
+    }
+  );
+
+  return elem != end(kCrashPingWhitelist);
+}
+
+bool
+IsAnnotationBlacklistedForContent(Annotation aAnnotation)
+{
+  auto elem = find_if(
+    begin(kContentProcessBlacklist),
+    end(kContentProcessBlacklist),
+    [&aAnnotation](Annotation aElement) {
+      return aElement == aAnnotation;
+    }
+  );
+
+  return elem != end(kContentProcessBlacklist);
+}
+
+} // namespace CrashReporter
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/CrashAnnotations.h.in
@@ -0,0 +1,76 @@
+/* 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 CrashAnnotations_h
+#define CrashAnnotations_h
+
+#include <cstdint>
+
+namespace CrashReporter {
+
+// Typed enum representing all crash annotations
+enum class Annotation : uint32_t {
+${enum}
+};
+
+// Stringified crash annotation names
+const char* const kAnnotationStrings[] = {
+${strings}
+};
+
+// Whitelist of crash annotations that can be included in a crash ping
+const Annotation kCrashPingWhitelist[] = {
+${whitelist}
+};
+
+// Blacklist of crash annotations that shouldn't be read from a content process
+const Annotation kContentProcessBlacklist[] = {
+${blacklist}
+};
+
+/**
+ * Return the string representation of a crash annotation.
+ *
+ * @param aAnnotation a crash annotation
+ * @returns A constant string holding the annotation name
+ */
+static inline const char*
+AnnotationToString(Annotation aAnnotation) {
+  return kAnnotationStrings[static_cast<uint32_t>(aAnnotation)];
+}
+
+/**
+ * Converts a string to its corresponding crash annotation.
+ *
+ * @param aResult a reference where the annotation will be stored
+ * @param aValue the string to be converted
+ * @return true if the string was successfully converted, false if it did not
+ *         correspond to any known annotation
+ */
+bool AnnotationFromString(Annotation& aResult, const char* aValue);
+
+/**
+ * Checks if the given crash annotation is whitelisted for inclusion in the
+ * crash ping.
+ *
+ * @param aAnnotation the crash annotation to be checked
+ * @return true if the annotation can be included in the crash ping, false
+ *         otherwise
+ */
+bool IsAnnotationWhitelistedForPing(Annotation aAnnotation);
+
+/**
+ * Checks if the given crash annotation needs to be filtered out when reading
+ * a content process crash annotations. Blacklisted annotations will be
+ * replaced with ones provided by the parent process.
+ *
+ * @param aAnnotation the crash annotation to be checked
+ * @return true if the annotation needs to be filtered out when reading
+ *         annotations provided by a content process, false otherwise
+ */
+bool IsAnnotationBlacklistedForContent(Annotation aAnnotation);
+
+} // namespace CrashReporter
+
+#endif // CrashAnnotations_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/CrashAnnotations.yaml
@@ -0,0 +1,209 @@
+# This lists all the available crash annotations.
+#
+# Mandatory fields for each entry are:
+# - description: A string describing the annotation
+# - type: the annotation type, currently `string`, `integer` or `boolean`.
+#   The latter are stringified to `1` for true and `0` for false.
+#
+# Additionally a field can have the following optional fields:
+# - altname: A string that will be used when writing out the annotation to the
+#   .extra file instead of the annotation name
+# - ping: A boolean that indicates whether the annotation is whitelisted for
+#   going into the crash ping, if not specified this defaults to false
+# - content: A boolean that indicates whether the field will be included in
+#   subprocess reports, if not specified this defaults to true
+
+AvailablePageFile:
+  description: >
+    Windows-only, maximum amount of memory that can be committed.  This
+    annotation is populated with the contents of the MEMORYSTATUSEX's structure
+    ullAvailPageFile field.
+  type: string
+  ping: true
+
+AvailablePhysicalMemory:
+  description: >
+    Windows-only, amount of free physical memory in bytes. This annotation
+    is populated with the contents of the MEMORYSTATUSEX's structure
+    ullAvailPhys field.
+  type: string
+  ping: true
+
+AvailableVirtualMemory:
+  description: >
+    Windows-only, amount of free virtual memory in bytes. This annotation is
+    populated with the contents of the MEMORYSTATUSEX's structure
+    ullAvailVirtual field.
+  type: string
+  ping: true
+
+BlockedDllList:
+  description: >
+    Comma-separated list of blocked DLLS, Windows-only
+  type: string
+  ping: true
+
+BlocklistInitFailed:
+  description: >
+    Set to 1 if the DLL blocklist could not be initialized.
+  type: boolean
+  ping: true
+
+BreakpadReserveAddress:
+  description: >
+    Address of the buffer reserved by Breakpad.
+  type: string
+
+BreakpadReserveSize:
+  description: >
+    Size of the buffer reserved by Breakpad.
+  type: string
+
+ContainsMemoryReport:
+  description: >
+    Indicates that the crash dump contains a memory report.
+  type: boolean
+  ping: true
+
+CrashTime:
+  description: >
+    Crash time in seconds since the Epoch.
+  type: string
+  ping: true
+
+EventLoopNestingLevel:
+  description: >
+    Present only if higher than 0, indicates that we're running in a nested
+    event loop and indicates the nesting level.
+  type: integer
+  ping: true
+
+InstallTime:
+  description: >
+    The time when Firefox was installed expressed as seconds since the Epoch
+  type: integer
+
+IsGarbageCollecting:
+  description: >
+    If true then the JavaScript garbage collector was running when the crash
+    occurred.
+  type: boolean
+  ping: true
+
+MozCrashReason:
+  description: >
+    Plaintext description of why Firefox crashed, this is usually set by
+    assertions and the like.
+  type: string
+  ping: true
+
+Notes:
+  description: >
+    Miscellaneous notes that can be appended to a crash.
+  type: string
+
+OOMAllocationSize:
+  description: >
+    Size of the allocation that caused an out-of-memory condition.
+  type: string
+  ping: true
+
+PluginHang:
+  description: >
+    The presence of this annotation indicates that this crash was generated in
+    response to a plugin hanging.
+  type: boolean
+
+ProcessType:
+  description: >
+    Type of the process that crashed, can hold the values "content", "plugin" or
+    "gpu" currently.
+  type: string
+
+SecondsSinceLastCrash:
+  description: >
+    Time in seconds since the last crash occurred.
+  type: string
+  ping: true
+
+ServerURL:
+  description: >
+    URL used to post the crash report.
+  type: string
+
+StartupTime:
+  description: >
+    The time when Firefox was launched expressed in seconds since the Epoch.
+  type: integer
+  content: false
+
+SystemMemoryUsePercentage:
+  description: >
+    Windows-only, percentage of physical memory in use. This annotation is
+    populated with the contents of the MEMORYSTATUSEX's structure dwMemoryLoad
+    field.
+  type: integer
+  ping: true
+
+TelemetrySessionId:
+  description: >
+    Telemetry session ID.
+  type: string
+
+TestKey:
+  description: >
+    Annotation used in tests.
+  type: string
+
+TestUnicode:
+  description: >
+    Annotation used in tests.
+  type: string
+
+TextureUsage:
+  description: >
+    Amount of memory in bytes consumed by textures.
+  type: string
+  ping: true
+
+ThreadIdNameMapping:
+  description: >
+    List of thread names with their corresponding thread IDs.
+  type: string
+
+TotalPageFile:
+  description: >
+    Windows-only, current committed memory limit. This annotation is
+    populated with the contents of the MEMORYSTATUSEX's structure
+    ullTotalPageFile field.
+  type: string
+  ping: true
+
+TotalPhysicalMemory:
+  description: >
+    Windows-only, amount of physical memory in bytes. This annotation is
+    populated with the contents of the MEMORYSTATUSEX's structure
+    ullTotalPhys field.
+  type: string
+  ping: true
+
+TotalVirtualMemory:
+  description: >
+    Windows-only, size of the virtual address space. This annotation is
+    populated with the contents of the MEMORYSTATUSEX's structure
+    ullTotalVirtual field.
+  type: string
+  ping: true
+
+UptimeTS:
+  description: >
+    Uptime in seconds. This annotation uses a string instead of an integer
+    because it has a fractional component.
+  type: string
+  ping: true
+
+User32BeforeBlocklist:
+  description: >
+    Set to 1 if user32.dll was loaded before we could install the DLL blocklist.
+  type: boolean
+  ping: true
--- a/toolkit/crashreporter/client/moz.build
+++ b/toolkit/crashreporter/client/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 if CONFIG['OS_TARGET'] != 'Android':
     Program('crashreporter')
 
     UNIFIED_SOURCES += [
+        '../CrashAnnotations.cpp',
         'crashreporter.cpp',
         'ping.cpp',
     ]
 
     LOCAL_INCLUDES += [
         '/toolkit/components/jsoncpp/include',
     ]
 
--- a/toolkit/crashreporter/client/ping.cpp
+++ b/toolkit/crashreporter/client/ping.cpp
@@ -15,16 +15,18 @@
 #elif defined(XP_MACOSX)
 #include <CoreFoundation/CoreFoundation.h>
 #elif defined(XP_WIN)
 #include <objbase.h>
 #endif
 
 #include "json/json.h"
 
+#include "CrashAnnotations.h"
+
 using std::string;
 
 namespace CrashReporter {
 
 struct UUID {
     uint32_t m0;
     uint16_t m1;
     uint16_t m2;
@@ -115,54 +117,25 @@ const char kTelemetryUrl[]       = "Tele
 const char kTelemetrySessionId[] = "TelemetrySessionId";
 const int  kTelemetryVersion     = 4;
 
 // Create the payload.metadata node of the crash ping using fields extracted
 // from the .extra file
 static Json::Value
 CreateMetadataNode(StringTable& strings)
 {
-  // The following list should be kept in sync with the one in CrashManager.jsm
-  const char *entries[] = {
-    "AsyncShutdownTimeout",
-    "AvailablePageFile",
-    "AvailablePhysicalMemory",
-    "AvailableVirtualMemory",
-    "BlockedDllList",
-    "BlocklistInitFailed",
-    "BuildID",
-    "ContainsMemoryReport",
-    "CrashTime",
-    "EventLoopNestingLevel",
-    "ipc_channel_error",
-    "IsGarbageCollecting",
-    "MozCrashReason",
-    "OOMAllocationSize",
-    "ProductID",
-    "ProductName",
-    "ReleaseChannel",
-    "RemoteType",
-    "SecondsSinceLastCrash",
-    "ShutdownProgress",
-    "StartupCrash",
-    "SystemMemoryUsePercentage",
-    "TextureUsage",
-    "TotalPageFile",
-    "TotalPhysicalMemory",
-    "TotalVirtualMemory",
-    "UptimeTS",
-    "User32BeforeBlocklist",
-    "Version",
-  };
-
   Json::Value node;
 
-  for (auto entry : entries) {
-    if ((strings.find(entry) != strings.end()) && !strings[entry].empty()) {
-      node[entry] = strings[entry];
+  for (auto line : strings) {
+    Annotation annotation;
+
+    if (AnnotationFromString(annotation, line.first.c_str())) {
+      if (IsAnnotationWhitelistedForPing(annotation)) {
+        node[line.first] = line.second;
+      }
     }
   }
 
   return node;
 }
 
 // Create the payload node of the crash ping
 static Json::Value
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/generate_crash_reporter_sources.py
@@ -0,0 +1,176 @@
+# 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/.
+
+import string
+import sys
+import yaml
+
+###############################################################################
+# Language-agnostic functionality                                             #
+###############################################################################
+
+
+def read_annotations(annotations_filename):
+    """Read the annotations from a YAML file.
+    If an error is encountered quit the program."""
+
+    try:
+        with open(annotations_filename, 'r') as annotations_file:
+            annotations = yaml.safe_load(annotations_file)
+
+    except (IOError, ValueError) as e:
+        print("Error parsing " + annotations_filename + ":\n" + str(e) + "\n")
+        sys.exit(1)
+
+    return annotations
+
+
+def read_template(template_filename):
+    """Read the contents of the template.
+    If an error is encountered quit the program."""
+
+    try:
+        with open(template_filename, "r") as template_file:
+            template = template_file.read()
+
+    except IOError as ex:
+        print("Error when reading " + template_filename + ":\n" + str(ex) + "\n")
+        sys.exit(1)
+
+    return template
+
+
+def extract_crash_ping_whitelist(annotations):
+    """Extract an array holding the names of the annotations whitelisted for
+    inclusion in the crash ping."""
+
+    return [name
+            for (name, data)
+            in annotations.iteritems()
+            if data.get('ping', False)]
+
+
+def extract_content_process_blacklist(annotations):
+    """Extract an array holding the names of the annotations blacklisted when
+    read from a content process."""
+
+    return [name
+            for (name, data)
+            in annotations.iteritems()
+            if not data.get('content', True)]
+
+###############################################################################
+# C++ code generation                                                         #
+###############################################################################
+
+
+def generate_strings(annotations):
+    """Generate strings corresponding to every annotation."""
+
+    names = ["  \"" + data.get('altname', name) + "\""
+             for (name, data)
+             in annotations.iteritems()]
+
+    return ',\n'.join(names)
+
+
+def generate_enum(annotations):
+    """Generate the C++ typed enum holding all the annotations and return it
+    as a string."""
+
+    enum = ""
+
+    for i, (name, _) in enumerate(annotations.iteritems()):
+        enum += "  " + name + " = " + str(i) + ",\n"
+
+    enum += "  Count = " + str(len(annotations))
+
+    return enum
+
+
+def generate_array_initializer(contents):
+    """Generates the initializer for a C++ array of annotations."""
+
+    initializer = ["  Annotation::" + name for name in contents]
+
+    return ',\n'.join(initializer)
+
+
+def generate_header(template, annotations):
+    """Generate a header by filling the template with the the list of
+    annotations and return it as a string."""
+
+    whitelist = extract_crash_ping_whitelist(annotations)
+    blacklist = extract_content_process_blacklist(annotations)
+
+    return "/* This file was autogenerated by " + \
+           "toolkit/crashreporter/generate_crash_reporter_sources.py. " + \
+           "DO NOT EDIT */\n\n" + \
+           string.Template(template).substitute({
+               "enum": generate_enum(annotations),
+               "strings": generate_strings(annotations),
+               "whitelist": generate_array_initializer(whitelist),
+               "blacklist": generate_array_initializer(blacklist),
+           })
+
+
+def emit_header(output, template_filename, annotations_filename):
+    """Generate the C++ header from the template and write it out."""
+
+    annotations = read_annotations(annotations_filename)
+    template = read_template(template_filename)
+    generated_header = generate_header(template, annotations)
+
+    try:
+        output.write(generated_header)
+
+    except IOError as ex:
+        print("Error while writing out the generated file:\n" + str(ex) + "\n")
+        sys.exit(1)
+
+###############################################################################
+# IDL code generation                                                         #
+###############################################################################
+
+
+def generate_annotations_constants(annotations):
+    """Generate the list of IDL constants corresponding to the annotations
+    and return it as a string."""
+
+    constants = []
+
+    for i, (name, _) in enumerate(annotations.iteritems()):
+        constants.append("  const long " + name + " = " + str(i + 1) + ";")
+
+    return "\n".join(constants)
+
+
+def generate_idl(template, annotations):
+    """Generate the IDL interface by filling the template with the the list of
+    annotations and return it as a string."""
+
+    annotations_list = generate_annotations_constants(annotations)
+
+    return "/* This file was autogenerated by " + \
+           "toolkit/crashreporter/generate_crash_reporter_sources.py. " + \
+           "DO NOT EDIT */\n\n" + \
+           string.Template(template).substitute({
+               "annotations": annotations_list,
+           })
+
+
+def emit_idl(output, template_filename, annotations_filename):
+    """Generate the nsICrashReporter IDL interface from the template and write
+    it out."""
+
+    annotations = read_annotations(annotations_filename)
+    template = read_template(template_filename)
+    generated_idl = generate_idl(template, annotations)
+
+    try:
+        output.write(generated_idl)
+
+    except IOError as ex:
+        print("Error while writing out the generated file:\n" + str(ex) + "\n")
+        sys.exit(1)
--- a/toolkit/crashreporter/moz.build
+++ b/toolkit/crashreporter/moz.build
@@ -4,23 +4,29 @@
 # 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/.
 
 SPHINX_TREES['crashreporter'] = 'docs'
 
 with Files('docs/**'):
     SCHEDULES.exclusive = ['docs']
 
+GENERATED_FILES += [
+    'CrashAnnotations.h',
+]
+
 EXPORTS += [
+    '!CrashAnnotations.h',
     'nsExceptionHandler.h',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 UNIFIED_SOURCES = [
+    'CrashAnnotations.cpp',
     'nsExceptionHandlerUtils.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_CRASHREPORTER']:
     if CONFIG['OS_ARCH'] == 'WINNT':
         DIRS += [
@@ -118,11 +124,18 @@ if CONFIG['MOZ_CRASHREPORTER']:
 
     if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
         CXXFLAGS += ['-Wno-shadow']
 else:
     UNIFIED_SOURCES += [
         'nsDummyExceptionHandler.cpp',
     ]
 
+# Generate CrashAnnotations.h
+crash_annotations = GENERATED_FILES['CrashAnnotations.h']
+crash_annotations.script = 'generate_crash_reporter_sources.py:emit_header'
+crash_annotations.inputs = [
+    'CrashAnnotations.h.in',
+    'CrashAnnotations.yaml',
+]
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Crash Reporting')
--- a/toolkit/crashreporter/nsDummyExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsDummyExceptionHandler.cpp
@@ -61,24 +61,41 @@ SetupExtraData(nsIFile* aAppDataDirector
 
 nsresult
 UnsetExceptionHandler()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult
-AnnotateCrashReport(const nsACString& key,
-                    const nsACString& data)
+AnnotateCrashReport(Annotation key, bool data)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+AnnotateCrashReport(Annotation key, int data)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult
-RemoveCrashReportAnnotation(const nsACString& key)
+AnnotateCrashReport(Annotation key, unsigned int data)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+AnnotateCrashReport(Annotation key, const nsACString& data)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+RemoveCrashReportAnnotation(Annotation key)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult
 SetGarbageCollecting(bool collecting)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -7,16 +7,17 @@
 #include "nsExceptionHandler.h"
 #include "nsExceptionHandlerUtils.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryService.h"
 #include "nsDataHashtable.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/EnumeratedRange.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Printf.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ipc/CrashReporterClient.h"
@@ -220,17 +221,17 @@ static XP_CHAR lastCrashTimeFilename[XP_
 // with the current process that gets lost when we fork so we need to
 // explicitly pass it to am
 static char* androidUserSerial = nullptr;
 #endif
 
 // this holds additional data sent via the API
 static Mutex* crashReporterAPILock;
 static Mutex* notesFieldLock;
-static AnnotationTable* crashReporterAPIData_Hash;
+static AnnotationTable crashReporterAPIData_Table;
 static nsCString* crashReporterAPIData = nullptr;
 static nsCString* crashEventAPIData = nullptr;
 static nsCString* notesField = nullptr;
 static bool isGarbageCollecting;
 static uint32_t eventloopNestingLevel = 0;
 
 // Avoid a race during application termination.
 static Mutex* dumpSafetyLock;
@@ -311,25 +312,16 @@ public:
 
   NS_IMETHOD Run() override;
 
 private:
   uint32_t mPID;
 };
 #endif // MOZ_CRASHREPORTER_INJECTOR
 
-// Crashreporter annotations that we don't send along in subprocess reports.
-static const char* kSubprocessBlacklist[] = {
-  "FramePoisonBase",
-  "FramePoisonSize",
-  "StartupCrash",
-  "StartupTime",
-  "URL"
-};
-
 // If annotations are attempted before the crash reporter is enabled,
 // they queue up here.
 class DelayedNote;
 nsTArray<nsAutoPtr<DelayedNote> >* gDelayedAnnotations;
 
 #if defined(XP_WIN)
 // the following are used to prevent other DLLs reverting the last chance
 // exception handler to the windows default. Any attempt to change the
@@ -665,23 +657,26 @@ WriteString(PlatformWriter& pw, const ch
   size_t len = my_strlen(str);
 #else
   size_t len = strlen(str);
 #endif
 
   pw.WriteBuffer(str, len);
 }
 
-template<int N>
 static void
-WriteAnnotation(PlatformWriter& pw, const char (&name)[N],
-                const char* value) {
-  WriteLiteral(pw, name);
+WriteAnnotation(PlatformWriter& pw, const Annotation name,
+                const char* value, size_t len = 0) {
+  WriteString(pw, AnnotationToString(name));
   WriteLiteral(pw, "=");
-  WriteString(pw, value);
+  if (len == 0) {
+    WriteString(pw, value);
+  } else {
+    pw.WriteBuffer(value, len);
+  }
   WriteLiteral(pw, "\n");
 };
 
 /**
  * If minidump_id is null, we assume that dump_path contains the full
  * dump file path.
  */
 static void
@@ -723,23 +718,28 @@ WriteGlobalMemoryStatus(PlatformWriter* 
     conversionFunc(statex.field, buffer, 10);                  \
     if (apiData) {                                             \
       WriteAnnotation(*apiData, name, buffer);                  \
     }                                                          \
     if (eventFile) {                                           \
       WriteAnnotation(*eventFile, name, buffer);                \
     }
 
-    WRITE_STATEX_FIELD(dwMemoryLoad, "SystemMemoryUsePercentage", ltoa);
-    WRITE_STATEX_FIELD(ullTotalVirtual, "TotalVirtualMemory", _ui64toa);
-    WRITE_STATEX_FIELD(ullAvailVirtual, "AvailableVirtualMemory", _ui64toa);
-    WRITE_STATEX_FIELD(ullTotalPageFile, "TotalPageFile", _ui64toa);
-    WRITE_STATEX_FIELD(ullAvailPageFile, "AvailablePageFile", _ui64toa);
-    WRITE_STATEX_FIELD(ullTotalPhys, "TotalPhysicalMemory", _ui64toa);
-    WRITE_STATEX_FIELD(ullAvailPhys, "AvailablePhysicalMemory", _ui64toa);
+    WRITE_STATEX_FIELD(dwMemoryLoad, Annotation::SystemMemoryUsePercentage,
+                       ltoa);
+    WRITE_STATEX_FIELD(ullTotalVirtual, Annotation::TotalVirtualMemory,
+                       _ui64toa);
+    WRITE_STATEX_FIELD(ullAvailVirtual, Annotation::AvailableVirtualMemory,
+                       _ui64toa);
+    WRITE_STATEX_FIELD(ullTotalPageFile, Annotation::TotalPageFile, _ui64toa);
+    WRITE_STATEX_FIELD(ullAvailPageFile, Annotation::AvailablePageFile,
+                       _ui64toa);
+    WRITE_STATEX_FIELD(ullTotalPhys, Annotation::TotalPhysicalMemory, _ui64toa);
+    WRITE_STATEX_FIELD(ullAvailPhys, Annotation::AvailablePhysicalMemory,
+                       _ui64toa);
 
 #undef WRITE_STATEX_FIELD
   }
 }
 #endif
 
 #if !defined(MOZ_WIDGET_ANDROID)
 
@@ -1019,101 +1019,108 @@ MinidumpCallback(
       OpenAPIData(apiData, descriptor.path());
 #else
       OpenAPIData(apiData, dump_path, minidump_id);
 #endif
       apiData.WriteBuffer(crashReporterAPIData->get(), crashReporterAPIData->Length());
     }
 
     if (currentSessionId) {
-      WriteAnnotation(apiData, "TelemetrySessionId", currentSessionId);
-      WriteAnnotation(eventFile, "TelemetrySessionId", currentSessionId);
+      WriteAnnotation(apiData, Annotation::TelemetrySessionId,
+                      currentSessionId);
+      WriteAnnotation(eventFile, Annotation::TelemetrySessionId,
+                      currentSessionId);
     }
 
-    WriteAnnotation(apiData, "CrashTime", crashTimeString);
-    WriteAnnotation(eventFile, "CrashTime", crashTimeString);
-
-    WriteAnnotation(apiData, "UptimeTS", uptimeTSString);
-    WriteAnnotation(eventFile, "UptimeTS", uptimeTSString);
+    WriteAnnotation(apiData, Annotation::CrashTime, crashTimeString);
+    WriteAnnotation(eventFile, Annotation::CrashTime, crashTimeString);
+
+    WriteAnnotation(apiData, Annotation::UptimeTS, uptimeTSString);
+    WriteAnnotation(eventFile, Annotation::UptimeTS, uptimeTSString);
 
     if (timeSinceLastCrash != 0) {
-      WriteAnnotation(apiData, "SecondsSinceLastCrash",
+      WriteAnnotation(apiData, Annotation::SecondsSinceLastCrash,
                       timeSinceLastCrashString);
-      WriteAnnotation(eventFile, "SecondsSinceLastCrash",
+      WriteAnnotation(eventFile, Annotation::SecondsSinceLastCrash,
                       timeSinceLastCrashString);
     }
     if (isGarbageCollecting) {
-      WriteAnnotation(apiData, "IsGarbageCollecting", "1");
-      WriteAnnotation(eventFile, "IsGarbageCollecting", "1");
+      WriteAnnotation(apiData, Annotation::IsGarbageCollecting, "1");
+      WriteAnnotation(eventFile, Annotation::IsGarbageCollecting, "1");
     }
 
     char buffer[128];
 
     if (eventloopNestingLevel > 0) {
       XP_STOA(eventloopNestingLevel, buffer, 10);
-      WriteAnnotation(apiData, "EventLoopNestingLevel", buffer);
-      WriteAnnotation(eventFile, "EventLoopNestingLevel", buffer);
+      WriteAnnotation(apiData, Annotation::EventLoopNestingLevel, buffer);
+      WriteAnnotation(eventFile, Annotation::EventLoopNestingLevel, buffer);
     }
 
 #ifdef XP_WIN
     if (gBreakpadReservedVM) {
       _ui64toa(uintptr_t(gBreakpadReservedVM), buffer, 10);
-      WriteAnnotation(apiData, "BreakpadReserveAddress", buffer);
+      WriteAnnotation(apiData, Annotation::BreakpadReserveAddress, buffer);
       _ui64toa(kReserveSize, buffer, 10);
-      WriteAnnotation(apiData, "BreakpadReserveSize", buffer);
+      WriteAnnotation(apiData, Annotation::BreakpadReserveSize, buffer);
     }
 
 #ifdef HAS_DLL_BLOCKLIST
     if (apiData.Valid()) {
-      DllBlocklist_WriteNotes(apiData.Handle());
-      DllBlocklist_WriteNotes(eventFile.Handle());
+      DllBlocklist_WriteNotes(
+        apiData.Handle(),
+        AnnotationToString(Annotation::BlockedDllList),
+        AnnotationToString(Annotation::BlocklistInitFailed),
+        AnnotationToString(Annotation::User32BeforeBlocklist));
+
+      DllBlocklist_WriteNotes(
+        eventFile.Handle(),
+        AnnotationToString(Annotation::BlockedDllList),
+        AnnotationToString(Annotation::BlocklistInitFailed),
+        AnnotationToString(Annotation::User32BeforeBlocklist));
     }
 #endif
     WriteGlobalMemoryStatus(&apiData, &eventFile);
 #endif // XP_WIN
 
     char* rust_panic_reason;
     size_t rust_panic_len;
     if (get_rust_panic_reason(&rust_panic_reason, &rust_panic_len)) {
       // rust_panic_reason is not null-terminated.
-      WriteLiteral(apiData, "MozCrashReason=");
-      apiData.WriteBuffer(rust_panic_reason, rust_panic_len);
-      WriteLiteral(apiData, "\n");
-      WriteLiteral(eventFile, "MozCrashReason=");
-      eventFile.WriteBuffer(rust_panic_reason, rust_panic_len);
-      WriteLiteral(eventFile, "\n");
+      WriteAnnotation(apiData, Annotation::MozCrashReason, rust_panic_reason,
+                      rust_panic_len);
+      WriteAnnotation(eventFile, Annotation::MozCrashReason, rust_panic_reason,
+                      rust_panic_len);
     } else if (gMozCrashReason) {
-      WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason);
-      WriteAnnotation(eventFile, "MozCrashReason", gMozCrashReason);
+      WriteAnnotation(apiData, Annotation::MozCrashReason, gMozCrashReason);
+      WriteAnnotation(eventFile, Annotation::MozCrashReason, gMozCrashReason);
     }
 
     if (oomAllocationSizeBuffer[0]) {
-      WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer);
-      WriteAnnotation(eventFile, "OOMAllocationSize", oomAllocationSizeBuffer);
+      WriteAnnotation(apiData, Annotation::OOMAllocationSize,
+                      oomAllocationSizeBuffer);
+      WriteAnnotation(eventFile, Annotation::OOMAllocationSize,
+                      oomAllocationSizeBuffer);
     }
 
     if (texturesSizeBuffer[0]) {
-      WriteAnnotation(apiData, "TextureUsage", texturesSizeBuffer);
-      WriteAnnotation(eventFile, "TextureUsage", texturesSizeBuffer);
+      WriteAnnotation(apiData, Annotation::TextureUsage, texturesSizeBuffer);
+      WriteAnnotation(eventFile, Annotation::TextureUsage, texturesSizeBuffer);
     }
 
     if (memoryReportPath) {
-      WriteLiteral(apiData, "ContainsMemoryReport=1\n");
-      WriteLiteral(eventFile, "ContainsMemoryReport=1\n");
+      WriteAnnotation(apiData, Annotation::ContainsMemoryReport, "1");
+      WriteAnnotation(eventFile, Annotation::ContainsMemoryReport, "1");
     }
 
     std::function<void(const char*)> getThreadAnnotationCB =
-      [&] (const char * aAnnotation) -> void {
-      if (aAnnotation) {
-        WriteLiteral(apiData, "ThreadIdNameMapping=");
-        WriteLiteral(eventFile, "ThreadIdNameMapping=");
-        WriteString(apiData, aAnnotation);
-        WriteString(eventFile, aAnnotation);
-        WriteLiteral(apiData, "\n");
-        WriteLiteral(eventFile, "\n");
+      [&] (const char* aValue) -> void {
+      if (aValue) {
+        WriteAnnotation(apiData, Annotation::ThreadIdNameMapping, aValue);
+        WriteAnnotation(eventFile, Annotation::ThreadIdNameMapping, aValue);
       }
     };
     GetFlatThreadAnnotation(getThreadAnnotationCB);
   }
 
   if (!doReport) {
 #ifdef XP_WIN
     TerminateProcess(GetCurrentProcess(), 1);
@@ -1270,36 +1277,34 @@ PrepareChildExceptionTimeAnnotations(voi
 #endif
 
   char oomAllocationSizeBuffer[32] = "";
   if (gOOMAllocationSize) {
     XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer, 10);
   }
 
   if (oomAllocationSizeBuffer[0]) {
-    WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer);
+    WriteAnnotation(apiData, Annotation::OOMAllocationSize,
+                    oomAllocationSizeBuffer);
   }
 
   char* rust_panic_reason;
   size_t rust_panic_len;
   if (get_rust_panic_reason(&rust_panic_reason, &rust_panic_len)) {
     // rust_panic_reason is not null-terminated.
-    WriteLiteral(apiData, "MozCrashReason=");
-    apiData.WriteBuffer(rust_panic_reason, rust_panic_len);
-    WriteLiteral(apiData, "\n");
+    WriteAnnotation(apiData, Annotation::MozCrashReason, rust_panic_reason,
+                    rust_panic_len);
   } else if (gMozCrashReason) {
-    WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason);
+    WriteAnnotation(apiData, Annotation::MozCrashReason, gMozCrashReason);
   }
 
   std::function<void(const char*)> getThreadAnnotationCB =
-    [&] (const char * aAnnotation) -> void {
-    if (aAnnotation) {
-      WriteLiteral(apiData, "ThreadIdNameMapping=");
-      WriteString(apiData, aAnnotation);
-      WriteLiteral(apiData, "\n");
+    [&] (const char * aValue) -> void {
+    if (aValue) {
+      WriteAnnotation(apiData, Annotation::ThreadIdNameMapping, aValue);
     }
   };
   GetFlatThreadAnnotation(getThreadAnnotationCB);
 }
 
 #ifdef XP_WIN
 static void
 ReserveBreakpadVM()
@@ -1492,20 +1497,16 @@ nsresult SetExceptionHandler(nsIFile* aX
   crashReporterAPIData = new nsCString();
   crashEventAPIData = new nsCString();
 
   NS_ASSERTION(!crashReporterAPILock, "Shouldn't have a lock yet");
   crashReporterAPILock = new Mutex("crashReporterAPILock");
   NS_ASSERTION(!notesFieldLock, "Shouldn't have a lock yet");
   notesFieldLock = new Mutex("notesFieldLock");
 
-  crashReporterAPIData_Hash =
-    new nsDataHashtable<nsCStringHashKey,nsCString>();
-  NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY);
-
   notesField = new nsCString();
   NS_ENSURE_TRUE(notesField, NS_ERROR_OUT_OF_MEMORY);
 
 #if !defined(MOZ_WIDGET_ANDROID)
   // Locate the crash reporter executable
   nsAutoString crashReporterPath_temp;
   nsresult rv = LocateExecutable(aXREDirectory,
                                  NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME),
@@ -1647,18 +1648,17 @@ nsresult SetExceptionHandler(nsIFile* aX
     printf_stderr ("SetUnhandledExceptionFilter hook failed; crash reporter is vulnerable.\n");
 #endif
 #endif
 
   // store application start time
   char timeString[32];
   time_t startupTime = time(nullptr);
   XP_TTOA(startupTime, timeString, 10);
-  AnnotateCrashReport(NS_LITERAL_CSTRING("StartupTime"),
-                      nsDependentCString(timeString));
+  AnnotateCrashReport(Annotation::StartupTime, nsDependentCString(timeString));
 
 #if defined(XP_MACOSX)
   // On OS X, many testers like to see the OS crash reporting dialog
   // since it offers immediate stack traces.  We allow them to set
   // a default to pass exceptions to the OS handler.
   Boolean keyExistsAndHasValidFormat = false;
   Boolean prefValue = ::CFPreferencesGetAppBooleanValue(CFSTR("OSCrashReporter"),
                                                         kCFPreferencesCurrentApplication,
@@ -1900,17 +1900,17 @@ nsresult SetupExtraData(nsIFile* aAppDat
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString data;
   if(NS_SUCCEEDED(GetOrInit(dataDirectory,
                             NS_LITERAL_CSTRING("InstallTime") + aBuildID,
                             data, InitInstallTime)))
-    AnnotateCrashReport(NS_LITERAL_CSTRING("InstallTime"), data);
+    AnnotateCrashReport(Annotation::InstallTime, data);
 
   // this is a little different, since we can't init it with anything,
   // since it's stored at crash time, and we can't annotate the
   // crash report with the stored value, since we really want
   // (now - LastCrash), so we just get a value if it exists,
   // and store it in a time_t value.
   if(NS_SUCCEEDED(GetOrInit(dataDirectory, NS_LITERAL_CSTRING("LastCrash"),
                             data, nullptr))) {
@@ -1958,18 +1958,19 @@ nsresult UnsetExceptionHandler()
   // allow SetUnhandledExceptionFilter
   gBlockUnhandledExceptionFilter = false;
 #endif
 
   delete gExceptionHandler;
 
   // do this here in the unlikely case that we succeeded in allocating
   // our strings but failed to allocate gExceptionHandler.
-  delete crashReporterAPIData_Hash;
-  crashReporterAPIData_Hash = nullptr;
+  std::fill(crashReporterAPIData_Table.begin(),
+            crashReporterAPIData_Table.end(),
+            EmptyCString());
 
   delete crashReporterAPILock;
   crashReporterAPILock = nullptr;
 
   delete notesFieldLock;
   notesFieldLock = nullptr;
 
   delete crashReporterAPIData;
@@ -2046,22 +2047,18 @@ static void ReplaceChar(nsCString& str, 
 
     str.BeginReading(iter);
     iter.advance(pos + replacement.Length() - 1);
     str.EndReading(end);
   }
 }
 
 static nsresult
-EscapeAnnotation(const nsACString& key, const nsACString& data, nsCString& escapedData)
+EscapeAnnotation(const nsACString& data, nsCString& escapedData)
 {
-  if (FindInReadable(NS_LITERAL_CSTRING("="), key) ||
-      FindInReadable(NS_LITERAL_CSTRING("\n"), key))
-    return NS_ERROR_INVALID_ARG;
-
   if (FindInReadable(NS_LITERAL_CSTRING("\0"), data))
     return NS_ERROR_INVALID_ARG;
 
   escapedData = data;
 
   // escape backslashes
   ReplaceChar(escapedData, NS_LITERAL_CSTRING("\\"),
               NS_LITERAL_CSTRING("\\\\"));
@@ -2069,35 +2066,35 @@ EscapeAnnotation(const nsACString& key, 
   ReplaceChar(escapedData, NS_LITERAL_CSTRING("\n"),
               NS_LITERAL_CSTRING("\\n"));
   return NS_OK;
 }
 
 class DelayedNote
 {
  public:
-  DelayedNote(const nsACString& aKey, const nsACString& aData)
-  : mKey(aKey), mData(aData), mType(Annotation) {}
+  DelayedNote(Annotation aKey, const nsACString& aData)
+  : mKey(aKey), mData(aData), mType(CrashAnnotation) {}
 
   explicit DelayedNote(const nsACString& aData)
   : mData(aData), mType(AppNote) {}
 
   void Run()
   {
-    if (mType == Annotation) {
+    if (mType == CrashAnnotation) {
       AnnotateCrashReport(mKey, mData);
     } else {
       AppendAppNotesToCrashReport(mData);
     }
   }
 
  private:
-  nsCString mKey;
+  Annotation mKey;
   nsCString mData;
-  enum AnnotationType { Annotation, AppNote } mType;
+  enum AnnotationType { CrashAnnotation, AppNote } mType;
 };
 
 static void
 EnqueueDelayedNote(DelayedNote* aNote)
 {
   if (!gDelayedAnnotations) {
     gDelayedAnnotations = new nsTArray<nsAutoPtr<DelayedNote> >();
   }
@@ -2111,64 +2108,86 @@ RunAndCleanUpDelayedNotes()
     for (nsAutoPtr<DelayedNote>& note : *gDelayedAnnotations) {
       note->Run();
     }
     delete gDelayedAnnotations;
     gDelayedAnnotations = nullptr;
   }
 }
 
-nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data)
+nsresult AnnotateCrashReport(Annotation key, bool data)
+{
+  return AnnotateCrashReport(key, data ? NS_LITERAL_CSTRING("1")
+                                       : NS_LITERAL_CSTRING("0"));
+}
+
+nsresult AnnotateCrashReport(Annotation key, int data)
+{
+  nsAutoCString dataString;
+  dataString.AppendInt(data);
+
+  return AnnotateCrashReport(key, dataString);
+}
+
+nsresult AnnotateCrashReport(Annotation key, unsigned int data)
+{
+  nsAutoCString dataString;
+  dataString.AppendInt(data);
+
+  return AnnotateCrashReport(key, dataString);
+}
+
+nsresult AnnotateCrashReport(Annotation key, const nsACString& data)
 {
   if (!GetEnabled())
     return NS_ERROR_NOT_INITIALIZED;
 
   nsCString escapedData;
-  nsresult rv = EscapeAnnotation(key, data, escapedData);
+  nsresult rv = EscapeAnnotation(data, escapedData);
   if (NS_FAILED(rv))
     return rv;
 
   if (!XRE_IsParentProcess()) {
     // The newer CrashReporterClient can be used from any thread.
     if (RefPtr<CrashReporterClient> client = CrashReporterClient::GetSingleton()) {
-      client->AnnotateCrashReport(nsCString(key), escapedData);
+      client->AnnotateCrashReport(key, escapedData);
       return NS_OK;
     }
 
     // EnqueueDelayedNote() can only be called on the main thread.
     MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
     EnqueueDelayedNote(new DelayedNote(key, data));
     return NS_OK;
   }
 
   MutexAutoLock lock(*crashReporterAPILock);
 
-  crashReporterAPIData_Hash->Put(key, escapedData);
+  crashReporterAPIData_Table[key] = escapedData;
 
   // now rebuild the file contents
   crashReporterAPIData->Truncate(0);
   crashEventAPIData->Truncate(0);
-  for (auto it = crashReporterAPIData_Hash->Iter(); !it.Done(); it.Next()) {
-    const nsACString& key = it.Key();
-    nsCString entry = it.Data();
+  for (auto key : MakeEnumeratedRange(Annotation::Count)) {
+    nsDependentCString str(AnnotationToString(key));
+    nsCString entry = crashReporterAPIData_Table[key];
     if (!entry.IsEmpty()) {
       NS_NAMED_LITERAL_CSTRING(kEquals, "=");
       NS_NAMED_LITERAL_CSTRING(kNewline, "\n");
-      nsAutoCString line = key + kEquals + entry + kNewline;
+      nsAutoCString line = str + kEquals + entry + kNewline;
 
       crashReporterAPIData->Append(line);
       crashEventAPIData->Append(line);
     }
   }
 
   return NS_OK;
 }
 
-nsresult RemoveCrashReportAnnotation(const nsACString& key)
+nsresult RemoveCrashReportAnnotation(Annotation key)
 {
   return AnnotateCrashReport(key, NS_LITERAL_CSTRING(""));
 }
 
 nsresult SetGarbageCollecting(bool collecting)
 {
   if (!GetEnabled())
     return NS_ERROR_NOT_INITIALIZED;
@@ -2197,17 +2216,17 @@ nsresult AppendAppNotesToCrashReport(con
   if (FindInReadable(NS_LITERAL_CSTRING("\0"), data))
     return NS_ERROR_INVALID_ARG;
 
   if (!XRE_IsParentProcess()) {
     // Since we don't go through AnnotateCrashReport in the parent process,
     // we must ensure that the data is escaped and valid before the parent
     // sees it.
     nsCString escapedData;
-    nsresult rv = EscapeAnnotation(NS_LITERAL_CSTRING("Notes"), data, escapedData);
+    nsresult rv = EscapeAnnotation(data, escapedData);
     if (NS_FAILED(rv))
       return rv;
 
     if (RefPtr<CrashReporterClient> client = CrashReporterClient::GetSingleton()) {
       client->AppendAppNotes(escapedData);
       return NS_OK;
     }
 
@@ -2216,29 +2235,30 @@ nsresult AppendAppNotesToCrashReport(con
 
     EnqueueDelayedNote(new DelayedNote(data));
     return NS_OK;
   }
 
   MutexAutoLock lock(*notesFieldLock);
 
   notesField->Append(data);
-  return AnnotateCrashReport(NS_LITERAL_CSTRING("Notes"), *notesField);
+  return AnnotateCrashReport(Annotation::Notes, *notesField);
 }
 
 // Returns true if found, false if not found.
 static bool
-GetAnnotation(const nsACString& key, nsACString& data)
+GetAnnotation(CrashReporter::Annotation key, nsACString& data)
 {
   if (!gExceptionHandler)
     return false;
 
-  nsAutoCString entry;
-  if (!crashReporterAPIData_Hash->Get(key, &entry))
+  nsCString entry = crashReporterAPIData_Table[key];
+  if (entry.IsEmpty()) {
     return false;
+  }
 
   data = entry;
   return true;
 }
 
 nsresult RegisterAppMemory(void* ptr, size_t length)
 {
   if (!GetEnabled())
@@ -2276,25 +2296,24 @@ void SetIncludeContextHeap(bool aValue)
 #endif
 }
 
 bool GetServerURL(nsACString& aServerURL)
 {
   if (!gExceptionHandler)
     return false;
 
-  return GetAnnotation(NS_LITERAL_CSTRING("ServerURL"), aServerURL);
+  return GetAnnotation(CrashReporter::Annotation::ServerURL, aServerURL);
 }
 
 nsresult SetServerURL(const nsACString& aServerURL)
 {
   // store server URL with the API data
   // the client knows to handle this specially
-  return AnnotateCrashReport(NS_LITERAL_CSTRING("ServerURL"),
-                             aServerURL);
+  return AnnotateCrashReport(Annotation::ServerURL, aServerURL);
 }
 
 nsresult
 SetRestartArgs(int argc, char** argv)
 {
   if (!gExceptionHandler)
     return NS_OK;
 
@@ -2907,102 +2926,90 @@ AppendExtraData(const nsAString& id, con
   if (!GetExtraFileForID(id, getter_AddRefs(extraFile)))
     return false;
   return AppendExtraData(extraFile, data);
 }
 
 //-----------------------------------------------------------------------------
 // Helpers for AppendExtraData()
 //
-struct Blacklist {
-  Blacklist() : mItems(nullptr), mLen(0) { }
-  Blacklist(const char** items, int len) : mItems(items), mLen(len) { }
-
-  bool Contains(const nsACString& key) const {
-    for (int i = 0; i < mLen; ++i)
-      if (key.EqualsASCII(mItems[i]))
-        return true;
-    return false;
-  }
-
-  const char** mItems;
-  const int mLen;
-};
-
 static void
-WriteAnnotation(PRFileDesc* fd, const nsACString& key, const nsACString& value)
+WriteAnnotation(PRFileDesc* fd, const Annotation key, const nsACString& value)
 {
-  PR_Write(fd, key.BeginReading(), key.Length());
+  const char* annotation = AnnotationToString(key);
+  PR_Write(fd, annotation, strlen(annotation));
   PR_Write(fd, "=", 1);
   PR_Write(fd, value.BeginReading(), value.Length());
   PR_Write(fd, "\n", 1);
 }
 
 template<int N>
 static void
 WriteLiteral(PRFileDesc* fd, const char (&str)[N])
 {
   PR_Write(fd, str, N - 1);
 }
 
 static bool
 WriteExtraData(nsIFile* extraFile,
                const AnnotationTable& data,
-               const Blacklist& blacklist,
                bool writeCrashTime=false,
-               bool truncate=false)
+               bool truncate=false,
+               bool content=false)
 {
   PRFileDesc* fd;
   int truncOrAppend = truncate ? PR_TRUNCATE : PR_APPEND;
   nsresult rv =
     extraFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | truncOrAppend,
                                 0600, &fd);
   if (NS_FAILED(rv))
     return false;
 
-  for (auto iter = data.ConstIter(); !iter.Done(); iter.Next()) {
-    // Skip entries in the blacklist.
-    const nsACString& key = iter.Key();
-    if (blacklist.Contains(key)) {
-        continue;
+  for (auto key : MakeEnumeratedRange(Annotation::Count)) {
+    nsCString value = data[key];
+    // Skip entries in the blacklist and empty entries.
+    if ((content && IsAnnotationBlacklistedForContent(key)) ||
+         value.IsEmpty()) {
+      continue;
     }
-    WriteAnnotation(fd, key, iter.Data());
+    WriteAnnotation(fd, key, value);
   }
 
   if (writeCrashTime) {
     time_t crashTime = time(nullptr);
     char crashTimeString[32];
     XP_TTOA(crashTime, crashTimeString, 10);
 
     WriteAnnotation(fd,
-                    nsDependentCString("CrashTime"),
+                    Annotation::CrashTime,
                     nsDependentCString(crashTimeString));
 
     double uptimeTS = (TimeStamp::NowLoRes() -
                        TimeStamp::ProcessCreation()).ToSecondsSigDigits();
     char uptimeTSString[64];
     SimpleNoCLibDtoA(uptimeTS, uptimeTSString, sizeof(uptimeTSString));
 
     WriteAnnotation(fd,
-                    nsDependentCString("UptimeTS"),
+                    Annotation::UptimeTS,
                     nsDependentCString(uptimeTSString));
   }
 
   if (memoryReportPath) {
-    WriteLiteral(fd, "ContainsMemoryReport=1\n");
+    WriteAnnotation(fd, Annotation::ContainsMemoryReport,
+                    NS_LITERAL_CSTRING("1"));
   }
 
   PR_Close(fd);
   return true;
 }
 
 bool
 AppendExtraData(nsIFile* extraFile, const AnnotationTable& data)
 {
-  return WriteExtraData(extraFile, data, Blacklist());
+  return WriteExtraData(extraFile, data);
 }
 
 static bool
 IsDataEscaped(char* aData)
 {
   if (strchr(aData, '\n')) {
     // There should not be any newlines
     return false;
@@ -3037,45 +3044,53 @@ ReadAndValidateExceptionTimeAnnotations(
     if (dataLen > 0 && data[dataLen - 1] == '\n') {
       data[dataLen - 1] = 0;
       --dataLen;
     }
     // There should not be any newlines in the key
     if (strchr(line, '\n')) {
       break;
     }
+    // The annotation sould be known
+    Annotation annotation;
+    if (!AnnotationFromString(annotation, line)) {
+      break;
+    }
     // Data should have been escaped by the child
     if (!IsDataEscaped(data)) {
       break;
     }
     // Looks good, save the (line,data) pair
-    aAnnotations.Put(nsDependentCString(line),
-                     nsDependentCString(data, dataLen));
+    aAnnotations[annotation] = nsDependentCString(data, dataLen);
   }
 }
 
 /**
+ * Writes extra data in the .extra file corresponding to the specified
+ * minidump. If `content` is set to true then this assumes that of a child
+ * process.
+ *
  * NOTE: One side effect of this function is that it deletes the
  * GeckoChildCrash<pid>.extra file if it exists, once processed.
  */
 static bool
 WriteExtraForMinidump(nsIFile* minidump,
                       uint32_t pid,
-                      const Blacklist& blacklist,
+                      bool content,
                       nsIFile** extraFile)
 {
   nsCOMPtr<nsIFile> extra;
   if (!GetExtraFileForMinidump(minidump, getter_AddRefs(extra))) {
     return false;
   }
 
-  if (!WriteExtraData(extra, *crashReporterAPIData_Hash,
-                      blacklist,
+  if (!WriteExtraData(extra, crashReporterAPIData_Table,
                       true /*write crash time*/,
-                      true /*truncate*/)) {
+                      true /*truncate*/,
+                      content)) {
     return false;
   }
 
   if (pid && processToCrashFd.count(pid)) {
     PRFileDesc* prFd = processToCrashFd[pid];
     processToCrashFd.erase(pid);
     FILE* fd;
 #if defined(XP_WIN)
@@ -3169,21 +3184,20 @@ OnChildProcessDumpRequested(void* aConte
 
   uint32_t pid =
 #ifdef XP_MACOSX
     aClientInfo.pid();
 #else
     aClientInfo->pid();
 #endif
 
-  if (!WriteExtraForMinidump(minidump, pid,
-                             Blacklist(kSubprocessBlacklist,
-                                       ArrayLength(kSubprocessBlacklist)),
-                             getter_AddRefs(extraFile)))
+  if (!WriteExtraForMinidump(minidump, pid, /* content */ true,
+                             getter_AddRefs(extraFile))) {
     return;
+  }
 
   if (ShouldReport()) {
     nsCOMPtr<nsIFile> memoryReport;
     if (memoryReportPath) {
       CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport));
       MOZ_ASSERT(memoryReport);
     }
     MoveToPending(minidump, extraFile, memoryReport);
@@ -3696,17 +3710,18 @@ PairedDumpCallbackExtra(
 #ifdef XP_WIN32
                      nullptr, nullptr,
 #endif
                      succeeded);
 
   nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context);
 
   nsCOMPtr<nsIFile> extra;
-  return WriteExtraForMinidump(minidump, 0, Blacklist(), getter_AddRefs(extra));
+  return WriteExtraForMinidump(minidump, 0, /* content */ false,
+                               getter_AddRefs(extra));
 }
 
 ThreadId
 CurrentThreadId()
 {
 #if defined(XP_WIN)
   return ::GetCurrentThreadId();
 #elif defined(XP_LINUX)
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -8,16 +8,19 @@
 // configured with --disable-crashreporter. If you add or remove a function
 // from this header you must update both implementations otherwise you'll break
 // builds that disable the crash reporter.
 
 #ifndef nsExceptionHandler_h__
 #define nsExceptionHandler_h__
 
 #include "mozilla/Assertions.h"
+#include "mozilla/EnumeratedArray.h"
+
+#include "CrashAnnotations.h"
 
 #include <functional>
 #include <stddef.h>
 #include <stdint.h>
 #include "nsError.h"
 #include "nsString.h"
 #include "prio.h"
 
@@ -32,18 +35,16 @@
 #include <mach/mach.h>
 #endif
 
 #if defined(XP_LINUX)
 #include <signal.h>
 #endif
 
 class nsIFile;
-template<class KeyClass, class DataType> class nsDataHashtable;
-class nsCStringHashKey;
 
 namespace CrashReporter {
 
 /**
  * Returns true if the crash reporter is using the dummy implementation.
  */
 static inline bool
 IsDummy() {
@@ -87,18 +88,21 @@ bool     GetServerURL(nsACString& aServe
 nsresult SetServerURL(const nsACString& aServerURL);
 bool     GetMinidumpPath(nsAString& aPath);
 nsresult SetMinidumpPath(const nsAString& aPath);
 
 
 // AnnotateCrashReport, RemoveCrashReportAnnotation and
 // AppendAppNotesToCrashReport may be called from any thread in a chrome
 // process, but may only be called from the main thread in a content process.
-nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data);
-nsresult RemoveCrashReportAnnotation(const nsACString& key);
+nsresult AnnotateCrashReport(Annotation key, bool data);
+nsresult AnnotateCrashReport(Annotation key, int data);
+nsresult AnnotateCrashReport(Annotation key, unsigned int data);
+nsresult AnnotateCrashReport(Annotation key, const nsACString& data);
+nsresult RemoveCrashReportAnnotation(Annotation key);
 nsresult AppendAppNotesToCrashReport(const nsACString& data);
 
 void AnnotateOOMAllocationSize(size_t size);
 void AnnotateTexturesSize(size_t size);
 nsresult SetGarbageCollecting(bool collecting);
 void SetEventloopNestingLevel(uint32_t level);
 void SetMinidumpAnalysisAllThreads();
 
@@ -108,17 +112,18 @@ nsresult SetupExtraData(nsIFile* aAppDat
 // Registers an additional memory region to be included in the minidump
 nsresult RegisterAppMemory(void* ptr, size_t length);
 nsresult UnregisterAppMemory(void* ptr);
 
 // Include heap regions of the crash context.
 void SetIncludeContextHeap(bool aValue);
 
 // Functions for working with minidumps and .extras
-typedef nsDataHashtable<nsCStringHashKey, nsCString> AnnotationTable;
+typedef mozilla::EnumeratedArray<Annotation, Annotation::Count, nsCString>
+        AnnotationTable;
 
 void DeleteMinidumpFilesForID(const nsAString& id);
 bool GetMinidumpForID(const nsAString& id, nsIFile** minidump);
 bool GetIDFromMinidump(nsIFile* minidump, nsAString& id);
 bool GetExtraFileForID(const nsAString& id, nsIFile** extraFile);
 bool GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile);
 bool AppendExtraData(const nsAString& id, const AnnotationTable& data);
 bool AppendExtraData(nsIFile* extraFile, const AnnotationTable& data);
--- a/toolkit/crashreporter/test/unit/test_crash_abort.js
+++ b/toolkit/crashreporter/test/unit/test_crash_abort.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   // Try crashing with an abort().
   do_crash(function() {
              crashType = CrashTestUtils.CRASH_ABORT;
-             crashReporter.annotateCrashReport("TestKey", "TestValue");
+             crashReporter.annotateCrashReport(crashReporter.TestKey, "TestValue");
            },
            function(mdump, extra) {
              Assert.equal(extra.TestKey, "TestValue");
            },
           // process will exit with a zero exit status
           true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js
+++ b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure.js
@@ -2,19 +2,19 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_after_js_large_allocation_failure.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
       Cu.getJSTestingFunctions().reportLargeAllocationFailure();
       Cu.forceGC();
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
       Assert.equal(false, "JSOutOfMemory" in extra);
       Assert.equal(extra.JSLargeAllocationFailure, "Recovered");
     },
     true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js
+++ b/toolkit/crashreporter/test/unit/test_crash_after_js_large_allocation_failure_reporting.js
@@ -2,23 +2,23 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_after_js_oom_reporting.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
 
       function crashWhileReporting() {
         CrashTestUtils.crash(crashType);
       }
 
       Services.obs.addObserver(crashWhileReporting, "memory-pressure");
       Cu.getJSTestingFunctions().reportLargeAllocationFailure();
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
       Assert.equal(extra.JSLargeAllocationFailure, "Reporting");
     },
     true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js
+++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_recovered.js
@@ -2,18 +2,18 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_after_js_oom_recovered.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
       Cu.getJSTestingFunctions().reportOutOfMemory();
       Cu.forceGC();
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
       Assert.equal(extra.JSOutOfMemory, "Recovered");
     },
     true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js
+++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported.js
@@ -2,26 +2,26 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_after_js_oom_reported.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
 
       // GC now to avoid having it happen randomly later, which would make the
       // test bogusly fail. See comment below.
       Cu.forceGC();
 
       Cu.getJSTestingFunctions().reportOutOfMemory();
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
 
       // The JSOutOfMemory field is absent if the JS engine never reported OOM,
       // "Reported" if it did, and "Recovered" if it reported OOM but
       // subsequently completed a full GC cycle. Since this test calls
       // reportOutOfMemory() and then crashes, we expect "Reported".
       //
       // Theoretically, GC can happen any time, so it is just possible that
       // this property could be "Recovered" even if the implementation is
--- a/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js
+++ b/toolkit/crashreporter/test/unit/test_crash_after_js_oom_reported_2.js
@@ -2,23 +2,23 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_after_js_oom_reported_2.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
       Cu.getJSTestingFunctions().reportOutOfMemory();
       Cu.forceGC(); // recover from first OOM
       Cu.getJSTestingFunctions().reportOutOfMemory();
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
 
       // Technically, GC can happen at any time, but it would be really
       // peculiar for it to happen again heuristically right after a GC was
       // forced. If extra.JSOutOfMemory is "Recovered" here, that's most
       // likely a bug in the error reporting machinery.
       Assert.equal(extra.JSOutOfMemory, "Reported");
     },
     true);
--- a/toolkit/crashreporter/test/unit/test_crash_moz_crash.js
+++ b/toolkit/crashreporter/test/unit/test_crash_moz_crash.js
@@ -1,13 +1,14 @@
 function run_test() {
   // Try crashing with a runtime abort
   do_crash(function() {
              crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-             crashReporter.annotateCrashReport("TestKey", "TestValue");
+             crashReporter.annotateCrashReport(crashReporter.TestKey,
+                                               "TestValue");
            },
            function(mdump, extra) {
              Assert.equal(extra.TestKey, "TestValue");
              Assert.equal(false, "OOMAllocationSize" in extra);
              Assert.equal(false, "JSOutOfMemory" in extra);
              Assert.equal(false, "JSLargeAllocationFailure" in extra);
            },
           // process will exit with a zero exit status
--- a/toolkit/crashreporter/test/unit/test_crash_oom.js
+++ b/toolkit/crashreporter/test/unit/test_crash_oom.js
@@ -2,17 +2,17 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
       Assert.ok("OOMAllocationSize" in extra);
       Assert.ok(Number(extra.OOMAllocationSize) > 0);
     },
     true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_purevirtual.js
+++ b/toolkit/crashreporter/test/unit/test_crash_purevirtual.js
@@ -8,16 +8,17 @@ function run_test() {
   if (isOSX) {
      dump("INFO | test_crash_purevirtual.js | TODO: purecalls not caught on OS X\n");
     return;
   }
 
   // Try crashing with a pure virtual call
   do_crash(function() {
              crashType = CrashTestUtils.CRASH_PURE_VIRTUAL_CALL;
-             crashReporter.annotateCrashReport("TestKey", "TestValue");
+             crashReporter.annotateCrashReport(crashReporter.TestKey,
+                                               "TestValue");
            },
            function(mdump, extra) {
              Assert.equal(extra.TestKey, "TestValue");
            },
           // process will exit with a zero exit status
           true);
 }
--- a/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
+++ b/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   // Try crashing with an uncaught exception.
   do_crash(function() {
              crashType = CrashTestUtils.CRASH_UNCAUGHT_EXCEPTION;
-             crashReporter.annotateCrashReport("TestKey", "TestValue");
+             crashReporter.annotateCrashReport(crashReporter.TestKey,
+                                               "TestValue");
            },
            function(mdump, extra) {
              Assert.equal(extra.TestKey, "TestValue");
            },
           // process will exit with a zero exit status
           true);
 }
 
--- a/toolkit/crashreporter/test/unit/test_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter.js
@@ -34,43 +34,60 @@ function run_test() {
   // check getting/setting minidumpPath
   // it should be $TEMP by default, but I'm not sure if we can exactly test that
   // this will at least test that it doesn't throw
   Assert.notEqual(cr.minidumpPath.path, "");
   var cwd = do_get_cwd();
   cr.minidumpPath = cwd;
   Assert.equal(cr.minidumpPath.path, cwd.path);
 
+  // Test annotateCrashReport()
   try {
-    cr.annotateCrashReport("equal=equal", "");
-    do_throw("Calling annotateCrashReport() with an '=' in key should have thrown!");
+    cr.annotateCrashReport(undefined, "");
+    do_throw("Calling annotateCrashReport() with an undefined key should have thrown!");
   } catch (ex) {
     Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
   try {
-    cr.annotateCrashReport("new\nline", "");
-    do_throw("Calling annotateCrashReport() with a '\\n' in key should have thrown!");
+    cr.annotateCrashReport(1000000, "");
+    do_throw("Calling annotateCrashReport() with a bogus key should have thrown!");
   } catch (ex) {
     Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
   try {
-    cr.annotateCrashReport("", "da\0ta");
+    cr.annotateCrashReport(cr.TestKey, "da\0ta");
     do_throw("Calling annotateCrashReport() with a '\\0' in data should have thrown!");
   } catch (ex) {
     Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
-  cr.annotateCrashReport("testKey", "testData1");
+  cr.annotateCrashReport(cr.TestKey, "testData1");
   // Replace previous data.
-  cr.annotateCrashReport("testKey", "testData2");
+  cr.annotateCrashReport(cr.TestKey, "testData2");
 
   try {
     cr.appendAppNotesToCrashReport("da\0ta");
     do_throw("Calling appendAppNotesToCrashReport() with a '\\0' in data should have thrown!");
   } catch (ex) {
     Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
   }
   cr.appendAppNotesToCrashReport("additional testData3");
   // Add more data.
   cr.appendAppNotesToCrashReport("additional testData4");
 
+  // Test removeCrashReportAnnotation()
+  try {
+    cr.removeCrashReportAnnotation(undefined);
+    do_throw("Calling removeCrashReportAnnotation() with an undefined key should have thrown!");
+  } catch (ex) {
+    Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
+  }
+  try {
+    cr.removeCrashReportAnnotation(1000000);
+    do_throw("Calling removeCrashReportAnnotation() with a bogus key should have thrown!");
+  } catch (ex) {
+    Assert.equal(ex.result, Cr.NS_ERROR_INVALID_ARG);
+  }
+  cr.removeCrashReportAnnotation(cr.TestKey);
+
+  // Testing setting the minidumpPath field
   cr.minidumpPath = cwd;
   Assert.equal(cr.minidumpPath.path, cwd.path);
 }
--- a/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
@@ -28,30 +28,37 @@ function run_test() {
              }
              if (is_win7_or_newer)
                Assert.ok(CrashTestUtils.dumpHasStream(mdump.path, CrashTestUtils.MD_MEMORY_INFO_LIST_STREAM));
            });
 
   // check setting some basic data
   do_crash(function() {
              // Add various annotations
-             crashReporter.annotateCrashReport("TestKey", "TestValue");
-             crashReporter.annotateCrashReport("\u2665", "\u{1F4A9}");
+             crashReporter.annotateCrashReport(crashReporter.TestKey,
+                                               "TestValue");
+             crashReporter.annotateCrashReport(crashReporter.TestUnicode,
+                                               "\u{1F4A9}");
+             // Some annotations have an alternate form when written out, Addons
+             // for example will be written out as Add-ons
+             crashReporter.annotateCrashReport(crashReporter.Addons,
+                                               "test%40mozilla.org:0.1");
              crashReporter.appendAppNotesToCrashReport("Junk");
              crashReporter.appendAppNotesToCrashReport("MoreJunk");
 
              // TelemetrySession setup will trigger the session annotation
              let scope = {};
              ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", scope);
              scope.TelemetryController.testSetup();
            },
            function(mdump, extra) {
              Assert.equal(extra.TestKey, "TestValue");
-             Assert.equal(extra["\u2665"], "\u{1F4A9}");
+             Assert.equal(extra.TestUnicode, "\u{1F4A9}");
              Assert.equal(extra.Notes, "JunkMoreJunk");
+             Assert.equal(extra["Add-ons"], "test%40mozilla.org:0.1");
              const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
              Assert.ok("TelemetrySessionId" in extra,
                        "The TelemetrySessionId field is present in the extra file");
              Assert.ok(UUID_REGEX.test(extra.TelemetrySessionId),
                        "The TelemetrySessionId is a UUID");
              Assert.ok(!("TelemetryClientId" in extra),
                        "The TelemetryClientId field is omitted by default");
              Assert.ok(!("TelemetryServerURL" in extra),
--- a/toolkit/crashreporter/test/unit/test_event_files.js
+++ b/toolkit/crashreporter/test/unit/test_event_files.js
@@ -19,17 +19,18 @@ add_task(async function test_main_proces
   let count = await new Promise((resolve, reject) => {
     do_crash(
       function() {
         // TelemetrySession setup will trigger the session annotation
         let scope = {};
         ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", scope);
         scope.TelemetryController.testSetup();
         crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-        crashReporter.annotateCrashReport("ShutdownProgress", "event-test");
+        crashReporter.annotateCrashReport(crashReporter.ShutdownProgress,
+                                          "event-test");
       },
       (minidump, extra) => {
         basename = minidump.leafName;
         cm._eventsDirs = [getEventDir()];
         cm.aggregateEventsFiles().then(resolve, reject);
       },
       true);
 
--- a/toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
+++ b/toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
@@ -2,20 +2,20 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_crash_oom.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
-      crashReporter.annotateCrashReport("TestingOOMCrash", "Yes");
+      crashReporter.annotateCrashReport(crashReporter.TestKey, "Yes");
     },
     function(mdump, extra) {
-      Assert.equal(extra.TestingOOMCrash, "Yes");
+      Assert.equal(extra.TestKey, "Yes");
       Assert.ok("OOMAllocationSize" in extra);
       Assert.ok(Number(extra.OOMAllocationSize) > 0);
       Assert.ok("SystemMemoryUsePercentage" in extra);
       Assert.ok("TotalVirtualMemory" in extra);
       Assert.ok("AvailableVirtualMemory" in extra);
       Assert.ok("TotalPageFile" in extra);
       Assert.ok("AvailablePageFile" in extra);
       Assert.ok("TotalPhysicalMemory" in extra);
--- a/toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
@@ -5,17 +5,18 @@ function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump("INFO | test_content_annotation.js | Can't test crashreporter in a non-libxul build.\n");
     return;
   }
 
   // Try crashing with a runtime abort
   do_content_crash(function() {
                      crashType = CrashTestUtils.CRASH_MOZ_CRASH;
-                     crashReporter.annotateCrashReport("TestKey", "TestValue");
+                     crashReporter.annotateCrashReport(crashReporter.TestKey,
+                                                       "TestValue");
                      crashReporter.appendAppNotesToCrashReport("!!!foo!!!");
                    },
                    function(mdump, extra) {
                      Assert.equal(extra.TestKey, "TestValue");
                      Assert.ok("StartupTime" in extra);
                      Assert.ok("ProcessType" in extra);
                      Assert.notEqual(extra.Notes.indexOf("!!!foo!!!"), -1);
                    });
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -250,16 +250,23 @@ static nsIProfileLock* gProfileLock;
 
 int    gRestartArgc;
 char **gRestartArgv;
 
 bool gIsGtest = false;
 
 nsString gAbsoluteArgv0Path;
 
+// This should always be 0 and it's used to catch JavaScript invocations of
+// nsICrashReporter.annotateCrashReporter() that are passed an undefined value.
+// Since undefined values are translated into 0 we need to special-case it.
+static const uint32_t kInvalidAnnotation = 0;
+static const uint32_t kAnnotationCount =
+  static_cast<uint32_t>(CrashReporter::Annotation::Count);
+
 #if defined(MOZ_WIDGET_GTK)
 #include <glib.h>
 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
 #define CLEANUP_MEMORY 1
 #define PANGO_ENABLE_BACKEND
 #include <pango/pangofc-fontmap.h>
 #endif
 #include <gtk/gtk.h>
@@ -1329,20 +1336,53 @@ nsXULAppInfo::GetExtraFileForID(const ns
   if (!CrashReporter::GetExtraFileForID(aId, aExtraFile)) {
     return NS_ERROR_FILE_NOT_FOUND;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXULAppInfo::AnnotateCrashReport(const nsACString& key,
+nsXULAppInfo::AnnotateCrashReport(uint32_t key,
                                   const nsACString& data)
 {
-  return CrashReporter::AnnotateCrashReport(key, data);
+  if (key == kInvalidAnnotation || (key > kAnnotationCount)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return CrashReporter::AnnotateCrashReport(
+    static_cast<CrashReporter::Annotation>(key - 1), data);
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::RemoveCrashReportAnnotation(uint32_t key)
+{
+  if (key == kInvalidAnnotation || (key > kAnnotationCount)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return CrashReporter::RemoveCrashReportAnnotation(
+    static_cast<CrashReporter::Annotation>(key - 1));
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::IsAnnotationWhitelistedForPing(const nsACString& aValue,
+                                             bool* aIsWhitelisted)
+{
+  nsAutoCString aValueStr(aValue);
+  CrashReporter::Annotation annotation;
+
+  if (CrashReporter::AnnotationFromString(annotation,
+                                          PromiseFlatCString(aValue).get())) {
+    *aIsWhitelisted = CrashReporter::IsAnnotationWhitelistedForPing(annotation);
+  } else {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULAppInfo::AppendAppNotesToCrashReport(const nsACString& data)
 {
   return CrashReporter::AppendAppNotesToCrashReport(data);
 }
 
--- a/xpcom/system/moz.build
+++ b/xpcom/system/moz.build
@@ -1,21 +1,33 @@
 # -*- 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/.
 
+GENERATED_FILES = [
+    'nsICrashReporter.idl',
+]
+
 XPIDL_SOURCES += [
+    '!nsICrashReporter.idl',
     'nsIBlocklistService.idl',
-    'nsICrashReporter.idl',
     'nsIDeviceSensors.idl',
     'nsIGConfService.idl',
     'nsIGeolocationProvider.idl',
     'nsIGIOService.idl',
     'nsIGSettingsService.idl',
     'nsIHapticFeedback.idl',
     'nsIPlatformInfo.idl',
     'nsIXULAppInfo.idl',
     'nsIXULRuntime.idl',
 ]
 
 XPIDL_MODULE = 'xpcom_system'
+
+# Generate nsICrashReporter.idl
+annotations_idl = GENERATED_FILES['nsICrashReporter.idl']
+annotations_idl.script = '/toolkit/crashreporter/generate_crash_reporter_sources.py:emit_idl'
+annotations_idl.inputs = [
+    'nsICrashReporter.idl.in',
+    '/toolkit/crashreporter/CrashAnnotations.yaml',
+]
rename from xpcom/system/nsICrashReporter.idl
rename to xpcom/system/nsICrashReporter.idl.in
--- a/xpcom/system/nsICrashReporter.idl
+++ b/xpcom/system/nsICrashReporter.idl.in
@@ -67,30 +67,55 @@ interface nsICrashReporter : nsISupports
    *        ID of the crash. Likely a UUID.
    *
    * @return The extra file associated with the ID.
    *
    * @throw NS_ERROR_FILE_NOT_FOUND if the extra file could not be found
    */
   nsIFile getExtraFileForID(in AString id);
 
+${annotations}
+
   /**
    * Add some extra data to be submitted with a crash report.
    *
    * @param key
-   *        Name of the data to be added.
+   *        One of the crash annotation constants.
    * @param data
    *        Data to be added.
    *
    * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized
-   * @throw NS_ERROR_INVALID_ARG if key or data contain invalid characters.
-   *                             Invalid characters for key are '=' and
-   *                             '\n'.  Invalid character for data is '\0'.
+   * @throw NS_ERROR_INVALID_ARG if key contains an invalid value or data
+   *                             contains invalid characters.  Invalid
+   *                             character for data is '\0'.
    */
-  void annotateCrashReport(in AUTF8String key, in AUTF8String data);
+  void annotateCrashReport(in unsigned long key, in AUTF8String data);
+
+  /**
+   * Remove a crash report annotation.
+   *
+   * @param key
+   *        One of the crash annotation constants.
+   *
+   * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized
+   * @throw NS_ERROR_INVALID_ARG if key contains an invalid value.
+   */
+  void removeCrashReportAnnotation(in unsigned long key);
+
+  /**
+   * Checks if an annotation is whitelisted for inclusion in the crash ping.
+   *
+   * @param key
+   *        One of the crash annotation strings.
+   *
+   * @return True if the specified value is a valid annotation and can be
+             included in the crash ping, false otherwise.
+   * @throw NS_ERROR_INVALID_ARG if key contains an invalid value.
+   */
+  boolean isAnnotationWhitelistedForPing(in ACString value);
 
   /**
    * Append some data to the "Notes" field, to be submitted with a crash report.
    * Unlike annotateCrashReport, this method will append to existing data.
    *
    * @param data
    *        Data to be added.
    *
@@ -116,17 +141,17 @@ interface nsICrashReporter : nsISupports
   /**
    * Write a minidump immediately, with the user-supplied exception
    * information. This is implemented on Windows only, because
    * SEH (structured exception handling) exists on Windows only.
    *
    * @param aExceptionInfo  EXCEPTION_INFO* provided by Window's SEH
    */
   [noscript] void writeMinidumpForException(in voidPtr aExceptionInfo);
-  
+
   /**
    * Append note containing an Obj-C exception's info.
    *
    * @param aException  NSException object to append note for
    */
   [noscript] void appendObjCExceptionInfoToAppNotes(in voidPtr aException);
 
   /**