Backed out changeset 1603f5abc56e (bug 1420363) for perma fails on test_busy_hang.xul. CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Sat, 16 Nov 2019 13:00:43 +0200
changeset 502321 ab0b99b9fbe77e8494816e20472ea593a29b598a
parent 502320 b81c1543943d460e5b3451d54705e67293e50005
child 502322 2909f92bb59434929217c1510b991ef7ed88314c
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1420363
milestone72.0a1
backs out1603f5abc56e6c2483e46ce577f6450e4c58733b
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 1603f5abc56e (bug 1420363) for perma fails on test_busy_hang.xul. CLOSED TREE
browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
dom/plugins/test/mochitest/hang_test.js
dom/plugins/test/mochitest/test_hangui.xul
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
mozglue/dllservices/WindowsDllBlocklist.cpp
mozglue/dllservices/WindowsDllBlocklist.h
testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
testing/specialpowers/content/SpecialPowersParent.jsm
toolkit/components/crashes/CrashManager.jsm
toolkit/components/crashes/CrashManagerTest.jsm
toolkit/components/crashes/CrashService.jsm
toolkit/components/crashes/docs/crash-events.rst
toolkit/components/crashes/tests/xpcshell/crash.extra
toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
toolkit/components/crashes/tests/xpcshell/test_crash_service.js
toolkit/crashreporter/CrashAnnotations.h.in
toolkit/crashreporter/CrashSubmit.jsm
toolkit/crashreporter/KeyValueParser.jsm
toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.cc
toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.h
toolkit/crashreporter/breakpad-patches/10-json-upload.patch
toolkit/crashreporter/client/crashreporter.cpp
toolkit/crashreporter/client/crashreporter.h
toolkit/crashreporter/client/crashreporter_gtk_common.cpp
toolkit/crashreporter/client/crashreporter_gtk_common.h
toolkit/crashreporter/client/crashreporter_linux.cpp
toolkit/crashreporter/client/crashreporter_osx.h
toolkit/crashreporter/client/crashreporter_osx.mm
toolkit/crashreporter/client/crashreporter_win.cpp
toolkit/crashreporter/client/ping.cpp
toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.h
toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.m
toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.cc
toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.h
toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
toolkit/crashreporter/moz.build
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/crashreporter/test/browser/crashreport.sjs
toolkit/crashreporter/test/browser/head.js
toolkit/crashreporter/test/unit/head_crashreporter.js
toolkit/crashreporter/test/unit/head_win64cfi.js
toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
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_phc.js
toolkit/crashreporter/test/unit/test_crash_purevirtual.js
toolkit/crashreporter/test/unit/test_crash_rust_panic.js
toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js
toolkit/crashreporter/test/unit/test_crash_terminator.js
toolkit/crashreporter/test/unit/test_crash_thread_annotation.js
toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
toolkit/crashreporter/test/unit/test_crash_with_memory_report.js
toolkit/crashreporter/test/unit/test_crashreporter.js
toolkit/crashreporter/test/unit/test_crashreporter_appmem.js
toolkit/crashreporter/test/unit/test_crashreporter_crash.js
toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
toolkit/crashreporter/test/unit/test_override_exception_handler.js
toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
toolkit/crashreporter/test/unit_ipc/test_content_exception_time_annotation.js
toolkit/crashreporter/test/unit_ipc/test_content_memory_list.js
toolkit/crashreporter/test/unit_ipc/test_content_oom_annotation_windows.js
toolkit/crashreporter/test/unit_ipc/test_content_phc.js
toolkit/crashreporter/test/unit_ipc/test_content_phc2.js
toolkit/crashreporter/test/unit_ipc/test_content_rust_panic.js
toolkit/crashreporter/test/unit_ipc/test_content_rust_panic_multiline.js
tools/lint/eslint/modules.json
--- a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
+++ b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
@@ -106,17 +106,17 @@ function createPendingCrashReports(howMa
     return Promise.all(promises);
   };
 
   let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
     Ci.nsIUUIDGenerator
   );
   // CrashSubmit expects there to be a ServerURL key-value
   // pair in the .extra file, so we'll satisfy it.
-  let extraFileContents = JSON.stringify({ ServerURL: SERVER_URL });
+  let extraFileContents = "ServerURL=" + SERVER_URL;
 
   return (async function() {
     let uuids = [];
     for (let i = 0; i < howMany; ++i) {
       let uuid = uuidGenerator.generateUUID().toString();
       // Strip the {}...
       uuid = uuid.substring(1, uuid.length - 1);
       await createFile(uuid, "dmp", accessDate);
--- a/dom/plugins/test/mochitest/hang_test.js
+++ b/dom/plugins/test/mochitest/hang_test.js
@@ -1,14 +1,15 @@
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+const { parseKeyValuePairsFromFile } = ChromeUtils.import(
+  "resource://gre/modules/KeyValueParser.jsm"
+);
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var success = false;
 var observerFired = false;
-var observerPromise = null;
 
 var testObserver = {
   idleHang: true,
 
   observe(subject, topic, data) {
     observerFired = true;
     ok(true, "Observer fired");
     is(topic, "plugin-crashed", "Checking correct topic");
@@ -25,47 +26,42 @@ var testObserver = {
     let pluginDumpFile = profD.clone();
     pluginDumpFile.append(pluginId + ".dmp");
     ok(pluginDumpFile.exists(), "plugin minidump exists");
 
     let pluginExtraFile = profD.clone();
     pluginExtraFile.append(pluginId + ".extra");
     ok(pluginExtraFile.exists(), "plugin extra file exists");
 
-    observerPromise = OS.File.read(pluginExtraFile.path, {
-      encoding: "utf-8",
-    }).then(json => {
-      let extraData = JSON.parse(json);
+    let extraData = parseKeyValuePairsFromFile(pluginExtraFile);
+
+    // check additional dumps
 
-      // check additional dumps
-      ok(
-        "additional_minidumps" in extraData,
-        "got field for additional minidumps"
-      );
-      let additionalDumps = extraData.additional_minidumps.split(",");
-      ok(
-        additionalDumps.includes("browser"),
-        "browser in additional_minidumps"
-      );
+    ok(
+      "additional_minidumps" in extraData,
+      "got field for additional minidumps"
+    );
+    let additionalDumps = extraData.additional_minidumps.split(",");
+    ok(additionalDumps.includes("browser"), "browser in additional_minidumps");
 
-      for (let name of additionalDumps) {
-        let file = profD.clone();
-        file.append(pluginId + "-" + name + ".dmp");
-        ok(file.exists(), "additional dump '" + name + "' exists");
-      }
+    for (let name of additionalDumps) {
+      let file = profD.clone();
+      file.append(pluginId + "-" + name + ".dmp");
+      ok(file.exists(), "additional dump '" + name + "' exists");
+    }
+
+    // check cpu usage field
 
-      // check cpu usage field
-      ok("PluginCpuUsage" in extraData, "got extra field for plugin cpu usage");
-      let cpuUsage = parseFloat(extraData.PluginCpuUsage);
-      if (this.idleHang) {
-        ok(cpuUsage == 0, "plugin cpu usage is 0%");
-      } else {
-        ok(cpuUsage > 0, "plugin cpu usage is >0%");
-      }
-    });
+    ok("PluginCpuUsage" in extraData, "got extra field for plugin cpu usage");
+    let cpuUsage = parseFloat(extraData.PluginCpuUsage);
+    if (this.idleHang) {
+      ok(cpuUsage == 0, "plugin cpu usage is 0%");
+    } else {
+      ok(cpuUsage > 0, "plugin cpu usage is >0%");
+    }
   },
 
   QueryInterface: ChromeUtils.generateQI([
     "nsIObserver",
     "nsISupportsWeakReference",
   ]),
 };
 
@@ -101,12 +97,10 @@ function onPluginCrashed(aEvent) {
   is(
     typeof aEvent.submittedCrashReport,
     "boolean",
     "submittedCrashReport is correct type"
   );
 
   Services.obs.removeObserver(testObserver, "plugin-crashed");
 
-  observerPromise.then(() => {
-    SimpleTest.finish();
-  });
+  SimpleTest.finish();
 }
--- a/dom/plugins/test/mochitest/test_hangui.xul
+++ b/dom/plugins/test/mochitest/test_hangui.xul
@@ -14,16 +14,18 @@
   <script type="application/javascript"
           src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hangui_common.js" />
 
 <body xmlns="http://www.w3.org/1999/xhtml">
   <iframe id="iframe1" src="hangui_subpage.html" width="400" height="400"></iframe>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
 SimpleTest.waitForExplicitFinish();
 SimpleTest.expectChildProcessCrash();
 SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
                                     true]]});
 setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
 const hangUITimeoutPref = "dom.ipc.plugins.hangUITimeoutSecs";
 const hangUIMinDisplayPref = "dom.ipc.plugins.hangUIMinDisplaySecs";
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -342,17 +342,17 @@ package org.mozilla.geckoview {
     field public final int count;
   }
 
   public class CrashReporter {
     ctor public CrashReporter();
     method @AnyThread @NonNull public static GeckoResult<String> sendCrashReport(@NonNull Context, @NonNull Intent, @NonNull String);
     method @AnyThread @NonNull public static GeckoResult<String> sendCrashReport(@NonNull Context, @NonNull Bundle, @NonNull String);
     method @AnyThread @NonNull public static GeckoResult<String> sendCrashReport(@NonNull Context, @NonNull File, @NonNull File, @NonNull String);
-    method @AnyThread @NonNull public static GeckoResult<String> sendCrashReport(@NonNull String, @NonNull File, @NonNull JSONObject);
+    method @AnyThread @NonNull public static GeckoResult<String> sendCrashReport(@NonNull Context, @NonNull File, @NonNull Map<String,String>, @NonNull String);
   }
 
   @UiThread public final class DynamicToolbarAnimator {
     method @Nullable public DynamicToolbarAnimator.ToolbarChromeProxy getToolbarChromeProxy();
     method public void hideToolbar(boolean);
     method public boolean isPinned();
     method public boolean isPinnedBy(@NonNull DynamicToolbarAnimator.PinReason);
     method public void setMaxToolbarHeight(int);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
@@ -10,18 +10,16 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Process;
 import android.util.Log;
-import org.json.JSONObject;
-import org.json.JSONException;
 
 import org.mozilla.geckoview.BuildConfig;
 import org.mozilla.geckoview.GeckoRuntime;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
@@ -414,28 +412,30 @@ public class CrashHandler implements Thr
 
         try {
             // Write out crash extra file as text.
 
             final Bundle extras = getCrashExtras(thread, exc);
             final String url = getServerUrl(extras);
             extras.putString("ServerURL", url);
 
-            JSONObject json = new JSONObject();
-            for (String key : extras.keySet()) {
-                json.put(key, extras.get(key));
-            }
-
             final BufferedWriter extraWriter = new BufferedWriter(new FileWriter(extraFile));
             try {
-                extraWriter.write(json.toString());
+                for (String key : extras.keySet()) {
+                    // Each extra line is in the format, key=value, with newlines escaped.
+                    extraWriter.write(key);
+                    extraWriter.write('=');
+                    extraWriter.write(String.valueOf(extras.get(key)).replace("\n", "\\n"));
+                    extraWriter.write('\n');
+                }
             } finally {
                 extraWriter.close();
             }
-        } catch (final IOException | JSONException e) {
+
+        } catch (final IOException e) {
             Log.e(LOGTAG, "Error writing extra file", e);
             return false;
         }
 
         return launchCrashReporter(dmpFile.getAbsolutePath(), extraFile.getAbsolutePath());
     }
 
     /**
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
@@ -4,56 +4,55 @@ import org.mozilla.gecko.util.ProxySelec
 
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.channels.Channels;
 import java.nio.channels.FileChannel;
 import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.zip.GZIPOutputStream;
 
 /**
  * Sends a crash report to the Mozilla  <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
  * crash report server.
  */
 public class CrashReporter {
     private static final String LOGTAG = "GeckoCrashReporter";
     private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
     private static final String PAGE_URL_KEY = "URL";
-    private static final String MINIDUMP_SHA256_HASH_KEY = "MinidumpSha256Hash";
     private static final String NOTES_KEY = "Notes";
     private static final String SERVER_URL_KEY = "ServerURL";
     private static final String STACK_TRACES_KEY = "StackTraces";
     private static final String PRODUCT_NAME_KEY = "ProductName";
     private static final String PRODUCT_ID_KEY = "ProductID";
     private static final String PRODUCT_ID = "{eeb82917-e434-4870-8148-5c03d4caa81b}";
     private static final List<String> IGNORE_KEYS = Arrays.asList(
+            NOTES_KEY,
             PAGE_URL_KEY,
             SERVER_URL_KEY,
             STACK_TRACES_KEY
     );
 
     /**
      * Sends a crash report to the Mozilla  <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
      * crash report server.
@@ -126,65 +125,100 @@ public class CrashReporter {
      * @see GeckoRuntime#ACTION_CRASHED
      */
     @AnyThread
     public static @NonNull GeckoResult<String> sendCrashReport(@NonNull final Context context,
                                                                @NonNull final File minidumpFile,
                                                                @NonNull final File extrasFile,
                                                                @NonNull final String appName)
             throws IOException, URISyntaxException {
-        final JSONObject annotations = getCrashAnnotations(context, minidumpFile, extrasFile, appName);
+        // Compute the minidump hash and generate the stack traces
+        computeMinidumpHash(extrasFile, minidumpFile);
 
-        final String url = annotations.optString(SERVER_URL_KEY, null);
-        if (url == null) {
-            return GeckoResult.fromException(new Exception("No server url present"));
-        }
+        // Extract the annotations from the .extra file
+        HashMap<String, String> extrasMap = readStringsFromFile(extrasFile.getPath());
 
-        for (String key : IGNORE_KEYS) {
-            annotations.remove(key);
-        }
-
-        return sendCrashReport(url, minidumpFile, annotations);
+        return sendCrashReport(context, minidumpFile, extrasMap, appName);
     }
 
     /**
      * Sends a crash report to the Mozilla  <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
      * crash report server.
      *
-     * @param serverURL The URL used to submit the crash report.
+     * @param context The current {@link Context}
      * @param minidumpFile A {@link File} referring to the minidump.
-     * @param extras A {@link JSONObject} holding the parsed JSON from the extra file.
+     * @param extras A {@link HashMap} with the parsed key-value pairs from the extras file.
+     * @param appName A human-readable app name.
      * @throws IOException This can be thrown if there was a networking error while sending the report.
      * @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
      * @return A GeckoResult containing the crash ID as a String.
      * @see GeckoRuntimeSettings.Builder#crashHandler(Class)
      * @see GeckoRuntime#ACTION_CRASHED
      */
     @AnyThread
     public static @NonNull GeckoResult<String> sendCrashReport(
-        @NonNull final String serverURL, @NonNull final File minidumpFile,
-        @NonNull final JSONObject extras)
-            throws IOException, URISyntaxException {
+        @NonNull final Context context, @NonNull final File minidumpFile,
+        @NonNull final Map<String, String> extras,
+        @NonNull final String appName) throws IOException, URISyntaxException {
         Log.d(LOGTAG, "Sending crash report: " + minidumpFile.getPath());
 
+        String spec = extras.get(SERVER_URL_KEY);
+        if (spec == null) {
+            return GeckoResult.fromException(new Exception("No server url present"));
+        }
+
+        extras.put(PRODUCT_NAME_KEY, appName);
+        extras.put(PRODUCT_ID_KEY, PRODUCT_ID);
+
         HttpURLConnection conn = null;
         try {
-            final URL url = new URL(URLDecoder.decode(serverURL, "UTF-8"));
+            final URL url = new URL(URLDecoder.decode(spec, "UTF-8"));
             final URI uri = new URI(url.getProtocol(), url.getUserInfo(),
                     url.getHost(), url.getPort(),
                     url.getPath(), url.getQuery(), url.getRef());
             conn = (HttpURLConnection) ProxySelector.openConnectionWithProxy(uri);
             conn.setRequestMethod("POST");
             String boundary = generateBoundary();
             conn.setDoOutput(true);
             conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
             conn.setRequestProperty("Content-Encoding", "gzip");
 
             OutputStream os = new GZIPOutputStream(conn.getOutputStream());
-            sendAnnotations(os, boundary, extras);
+            for (String key : extras.keySet()) {
+                if (IGNORE_KEYS.contains(key)) {
+                    Log.d(LOGTAG, "Ignoring: " + key);
+                    continue;
+                }
+
+                sendPart(os, boundary, key, extras.get(key));
+            }
+
+            StringBuilder sb = new StringBuilder();
+            sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
+            sb.append(Build.MANUFACTURER).append(' ')
+                    .append(Build.MODEL).append('\n')
+                    .append(Build.FINGERPRINT);
+            sendPart(os, boundary, NOTES_KEY, sb.toString());
+
+            sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
+            sendPart(os, boundary, "Android_Model", Build.MODEL);
+            sendPart(os, boundary, "Android_Board", Build.BOARD);
+            sendPart(os, boundary, "Android_Brand", Build.BRAND);
+            sendPart(os, boundary, "Android_Device", Build.DEVICE);
+            sendPart(os, boundary, "Android_Display", Build.DISPLAY);
+            sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
+            sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
+            sendPart(os, boundary, "Android_PackageName", context.getPackageName());
+            try {
+                sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
+                sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
+            } catch (Exception ex) {
+                Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
+            }
+            sendPart(os, boundary, "Android_Version",  Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
             sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
             os.write(("\r\n--" + boundary + "--\r\n").getBytes());
             os.flush();
             os.close();
 
             BufferedReader br = null;
             try {
                 br = new BufferedReader(
@@ -218,43 +252,76 @@ public class CrashReporter {
         } finally {
             if (conn != null) {
                 conn.disconnect();
             }
         }
         return GeckoResult.fromException(new Exception("Failed to submit crash report"));
     }
 
-    private static String computeMinidumpHash(@NonNull final File minidump) throws IOException {
-        MessageDigest md = null;
-        FileInputStream stream = new FileInputStream(minidump);
+    private static void computeMinidumpHash(final File extraFile, final File minidump) {
         try {
-            md = MessageDigest.getInstance("SHA-256");
+            FileInputStream stream = new FileInputStream(minidump);
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+            try {
+                byte[] buffer = new byte[4096];
+                int readBytes;
 
-            byte[] buffer = new byte[4096];
-            int readBytes;
+                while ((readBytes = stream.read(buffer)) != -1) {
+                    md.update(buffer, 0, readBytes);
+                }
+            } finally {
+                stream.close();
+            }
 
-            while ((readBytes = stream.read(buffer)) != -1) {
-                md.update(buffer, 0, readBytes);
+            byte[] digest = md.digest();
+            StringBuilder hash = new StringBuilder(84);
+
+            hash.append("MinidumpSha256Hash=");
+
+            for (int i = 0; i < digest.length; i++) {
+                hash.append(Integer.toHexString((digest[i] & 0xf0) >> 4));
+                hash.append(Integer.toHexString(digest[i] & 0x0f));
             }
-        } catch (NoSuchAlgorithmException e) {
-            throw new IOException(e);
-        } finally {
-            stream.close();
+
+            hash.append('\n');
+
+            FileWriter writer = new FileWriter(extraFile, /* append */ true);
+
+            try {
+                writer.write(hash.toString());
+            } finally {
+                writer.close();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "exception while computing the minidump hash: ", e);
         }
+    }
 
-        byte[] digest = md.digest();
-        StringBuilder hash = new StringBuilder(64);
+    private static HashMap<String, String> readStringsFromFile(final String filePath)
+            throws IOException {
+        FileReader fileReader = null;
+        BufferedReader bufReader = null;
+        try {
+            fileReader = new FileReader(filePath);
+            bufReader = new BufferedReader(fileReader);
+            return readStringsFromReader(bufReader);
+        } finally {
+            try {
+                if (fileReader != null) {
+                    fileReader.close();
+                }
 
-        for (int i = 0; i < digest.length; i++) {
-            hash.append(Integer.toHexString((digest[i] & 0xf0) >> 4));
-            hash.append(Integer.toHexString(digest[i] & 0x0f));
+                if (bufReader != null) {
+                    bufReader.close();
+                }
+            } catch (IOException e) {
+            }
         }
-
-        return hash.toString();
     }
 
     private static HashMap<String, String> readStringsFromReader(final BufferedReader reader)
             throws IOException {
         String line;
         HashMap<String, String> map = new HashMap<>();
         while ((line = reader.readLine()) != null) {
             int equalsPos = -1;
@@ -262,89 +329,34 @@ public class CrashReporter {
                 String key = line.substring(0, equalsPos);
                 String val = unescape(line.substring(equalsPos + 1));
                 map.put(key, val);
             }
         }
         return map;
     }
 
-    private static JSONObject readExtraFile(final String filePath)
-            throws IOException, JSONException {
-        byte[] buffer = new byte[4096];
-        FileInputStream inputStream = new FileInputStream(filePath);
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        int bytesRead = 0;
-
-        while ((bytesRead = inputStream.read(buffer)) != -1) {
-            outputStream.write(buffer, 0, bytesRead);
-        }
-
-        String contents = new String(outputStream.toByteArray(), "UTF-8");
-        return new JSONObject(contents);
-    }
-
-    private static JSONObject getCrashAnnotations(@NonNull final Context context,
-                                                  @NonNull final File minidump,
-                                                  @NonNull final File extra,
-                                                  @NonNull final String appName)
-            throws IOException {
-        try {
-            final JSONObject annotations = readExtraFile(extra.getPath());
-
-            // Compute the minidump hash and generate the stack traces
-            try {
-                final String hash = computeMinidumpHash(minidump);
-                annotations.put(MINIDUMP_SHA256_HASH_KEY, hash);
-            } catch (Exception e) {
-                Log.e(LOGTAG, "exception while computing the minidump hash: ", e);
-            }
-
-            annotations.put(PRODUCT_NAME_KEY, appName);
-            annotations.put(PRODUCT_ID_KEY, PRODUCT_ID);
-            annotations.put("Android_Manufacturer", Build.MANUFACTURER);
-            annotations.put("Android_Model", Build.MODEL);
-            annotations.put("Android_Board", Build.BOARD);
-            annotations.put("Android_Brand", Build.BRAND);
-            annotations.put("Android_Device", Build.DEVICE);
-            annotations.put("Android_Display", Build.DISPLAY);
-            annotations.put("Android_Fingerprint", Build.FINGERPRINT);
-            annotations.put("Android_CPU_ABI", Build.CPU_ABI);
-            annotations.put("Android_PackageName", context.getPackageName());
-            try {
-                annotations.put("Android_CPU_ABI2", Build.CPU_ABI2);
-                annotations.put("Android_Hardware", Build.HARDWARE);
-            } catch (Exception ex) {
-                Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
-            }
-            annotations.put("Android_Version",  Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
-
-            return annotations;
-        } catch (JSONException e) {
-            throw new IOException(e);
-        }
-    }
-
     private static String generateBoundary() {
         // Generate some random numbers to fill out the boundary
         int r0 = (int)(Integer.MAX_VALUE * Math.random());
         int r1 = (int)(Integer.MAX_VALUE * Math.random());
         return String.format("---------------------------%08X%08X", r0, r1);
     }
 
-    private static void sendAnnotations(final OutputStream os, final String boundary,
-                                        final JSONObject extras) throws IOException {
-        os.write(("--" + boundary + "\r\n" +
-                "Content-Disposition: form-data; name=\"extra\"; " +
-                "filename=\"extra.json\"\r\n" +
-                "Content-Type: application/json\r\n" +
-                "\r\n"
-        ).getBytes());
-        os.write(extras.toString().getBytes("UTF-8"));
-        os.write('\n');
+    private static void sendPart(final OutputStream os, final String boundary, final String name,
+                                 final String data) {
+        try {
+            os.write(("--" + boundary + "\r\n" +
+                    "Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
+                    "\r\n" +
+                    data + "\r\n"
+            ).getBytes());
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
+        }
     }
 
     private static void sendFile(final OutputStream os, final String boundary, final String name,
                                  final File file) throws IOException {
         os.write(("--" + boundary + "\r\n" +
                 "Content-Disposition: form-data; name=\"" + name + "\"; " +
                 "filename=\"" + file.getName() + "\"\r\n" +
                 "Content-Type: application/octet-stream\r\n" +
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -32,32 +32,27 @@ exclude: true
 - Added text selection action methods to [`SelectionActionDelegate.Selection`][72.8]
   ([bug 1581161]({{bugzilla}}1581161))
 - Added [`BasicSelectionActionDelegate.getSelection`][72.9]
   ([bug 1581161]({{bugzilla}}1581161))
 - Changed [`BasicSelectionActionDelegate.clearSelection`][72.10] to public.
   ([bug 1581161]({{bugzilla}}1581161))
 - Added `Autofill` commit support.
   ([bug 1577005]({{bugzilla}}1577005))
-- Changed [`CrashReporter#sendCrashReport(Context, File, JSONObject)`][72.11] to
-  accept a JSON object instead of a Map. Said object also includes the
-  application name that was previously passed as the fourth argument to the
-  method, which was thus removed.
 
 [72.1]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest#hasUserGesture-
 [72.2]: {{javadoc_uri}}/Autofill.html
 [72.3]: {{javadoc_uri}}/WebResponse.html#body
 [72.4]: {{javadoc_uri}}/WebResponse.html#setReadTimeoutMillis-long-
 [72.5]: {{javadoc_uri}}/WebResponse.html#DEFAULT_READ_TIMEOUT_MS
 [72.6]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.html#onShowActionRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection-
 [72.7]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#onShowActionRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection-
 [72.8]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.Selection.html
 [72.9]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#getSelection-
 [72.10]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#clearSelection-
-[72.11]: {{javadoc_uri}}/CrashReporter#sendCrashReport-android.content.Context-java.io.File-org.json.JSONObject-
 
 ## v71
 - Added a content blocking flag for blocked social cookies to [`ContentBlocking`][70.17].
   ([bug 1584479]({{bugzilla}}1584479))
 - Added [`onBooleanScalar`][71.1], [`onLongScalar`][71.2],
   [`onStringScalar`][71.3] to [`RuntimeTelemetry.Delegate`][70.12] to support
   scalars in streaming telemetry. ⚠️  As part of this change,
   `onTelemetryReceived` has been renamed to [`onHistogram`][71.4], and
@@ -445,9 +440,9 @@ exclude: true
 [65.19]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect
 [65.20]: {{javadoc_uri}}/GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER    
 [65.21]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html
 [65.22]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement-
 [65.23]: {{javadoc_uri}}/GeckoSession.FinderResult.html
 [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: {{javadoc_uri}}/GeckoResult.html
 
-[api-version]: 579e821b32aed4e3f51345e2cc1963a809883e9b
+[api-version]: 8d6a09b6a33550dffb6303dc01c5e6ff2d3cc499
--- a/mozglue/dllservices/WindowsDllBlocklist.cpp
+++ b/mozglue/dllservices/WindowsDllBlocklist.cpp
@@ -8,16 +8,17 @@
 
 #pragma warning(push)
 #pragma warning(disable : 4275 4530)  // See msvc-stl-wrapper.template.h
 #include <map>
 #pragma warning(pop)
 
 #include "Authenticode.h"
 #include "BaseProfiler.h"
+#include "CrashAnnotations.h"
 #include "nsAutoPtr.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/StackWalk_windows.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
@@ -41,17 +42,17 @@ namespace mozilla {
 glue::Win32SRWLock gDllServicesLock;
 glue::detail::DllServicesBase* gDllServices;
 
 }  // namespace mozilla
 
 using namespace mozilla;
 
 using CrashReporter::Annotation;
-using CrashReporter::AnnotationWriter;
+using CrashReporter::AnnotationToString;
 
 #define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
 #define DLL_BLOCKLIST_STRING_TYPE const char*
 #include "mozilla/WindowsDllBlocklistLegacyDefs.h"
 
 // define this for very verbose dll load debug spew
 #undef DEBUG_very_verbose
 
@@ -211,48 +212,28 @@ class ReentrancySentinel {
   static std::map<DWORD, const char*>* sThreadMap;
 
   const char* mPreviousDllName;
   bool mReentered;
 };
 
 std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap;
 
-class WritableBuffer {
- public:
-  WritableBuffer() : mBuffer{0}, mLen(0) {}
-
-  void Write(const char* aData, size_t aLen) {
-    size_t writable_len = std::min(aLen, Available());
-    memcpy(mBuffer + mLen, aData, writable_len);
-    mLen += writable_len;
-  }
-
-  size_t const Length() { return mLen; }
-  const char* Data() { return mBuffer; }
-
- private:
-  size_t const Available() { return sizeof(mBuffer) - mLen; }
-
-  char mBuffer[1024];
-  size_t mLen;
-};
-
 /**
  * This is a linked list of DLLs that have been blocked. It doesn't use
  * mozilla::LinkedList because this is an append-only list and doesn't need
  * to be doubly linked.
  */
 class DllBlockSet {
  public:
   static void Add(const char* name, unsigned long long version);
 
-  // Write the list of blocked DLLs to a WritableBuffer object. This method is
-  // run after a crash occurs and must therefore not use the heap, etc.
-  static void Write(WritableBuffer& buffer);
+  // Write the list of blocked DLLs to a file HANDLE. This method is run after
+  // a crash occurs and must therefore not use the heap, etc.
+  static void Write(HANDLE file);
 
  private:
   DllBlockSet(const char* name, unsigned long long version)
       : mName(name), mVersion(version), mNext(nullptr) {}
 
   const char* mName;  // points into the gWindowsDllBlocklist string
   unsigned long long mVersion;
   DllBlockSet* mNext;
@@ -270,46 +251,47 @@ void DllBlockSet::Add(const char* name, 
     }
   }
   // Not already present
   DllBlockSet* n = new DllBlockSet(name, version);
   n->mNext = gFirst;
   gFirst = n;
 }
 
-void DllBlockSet::Write(WritableBuffer& buffer) {
+void DllBlockSet::Write(HANDLE file) {
   // It would be nicer to use AutoCriticalSection here. However, its destructor
   // might not run if an exception occurs, in which case we would never leave
   // the critical section. (MSVC warns about this possibility.) So we
   // enter and leave manually.
   ::EnterCriticalSection(&sLock);
 
   // Because this method is called after a crash occurs, and uses heap memory,
   // protect this entire block with a structured exception handler.
   MOZ_SEH_TRY {
+    DWORD nBytes;
     for (DllBlockSet* b = gFirst; b; b = b->mNext) {
       // write name[,v.v.v.v];
-      buffer.Write(b->mName, strlen(b->mName));
+      WriteFile(file, b->mName, strlen(b->mName), &nBytes, nullptr);
       if (b->mVersion != DllBlockInfo::ALL_VERSIONS) {
-        buffer.Write(",", 1);
+        WriteFile(file, ",", 1, &nBytes, nullptr);
         uint16_t parts[4];
         parts[0] = b->mVersion >> 48;
         parts[1] = (b->mVersion >> 32) & 0xFFFF;
         parts[2] = (b->mVersion >> 16) & 0xFFFF;
         parts[3] = b->mVersion & 0xFFFF;
         for (int p = 0; p < 4; ++p) {
           char buf[32];
-          _ltoa_s(parts[p], buf, sizeof(buf), 10);
-          buffer.Write(buf, strlen(buf));
+          ltoa(parts[p], buf, 10);
+          WriteFile(file, buf, strlen(buf), &nBytes, nullptr);
           if (p != 3) {
-            buffer.Write(".", 1);
+            WriteFile(file, ".", 1, &nBytes, nullptr);
           }
         }
       }
-      buffer.Write(";", 1);
+      WriteFile(file, ";", 1, &nBytes, nullptr);
     }
   }
   MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}
 
   ::LeaveCriticalSection(&sLock);
 }
 
 static UniquePtr<wchar_t[]> getFullPath(PWCHAR filePath, wchar_t* fname) {
@@ -699,45 +681,54 @@ MFBT_API void DllBlocklist_Initialize(ui
   }
 #endif
 }
 
 #ifdef DEBUG
 MFBT_API void DllBlocklist_Shutdown() {}
 #endif  // DEBUG
 
-static void InternalWriteNotes(AnnotationWriter& aWriter) {
-  WritableBuffer buffer;
-  DllBlockSet::Write(buffer);
+static void WriteAnnotation(HANDLE aFile, Annotation aAnnotation,
+                            const char* aValue, DWORD* aNumBytes) {
+  const char* str = AnnotationToString(aAnnotation);
+  WriteFile(aFile, str, strlen(str), aNumBytes, nullptr);
+  WriteFile(aFile, "=", 1, aNumBytes, nullptr);
+  WriteFile(aFile, aValue, strlen(aValue), aNumBytes, nullptr);
+}
 
-  aWriter.Write(Annotation::BlockedDllList, buffer.Data(), buffer.Length());
+static void InternalWriteNotes(HANDLE file) {
+  DWORD nBytes;
+
+  WriteAnnotation(file, Annotation::BlockedDllList, "", &nBytes);
+  DllBlockSet::Write(file);
+  WriteFile(file, "\n", 1, &nBytes, nullptr);
 
   if (sBlocklistInitFailed) {
-    aWriter.Write(Annotation::BlocklistInitFailed, "1");
+    WriteAnnotation(file, Annotation::BlocklistInitFailed, "1\n", &nBytes);
   }
 
   if (sUser32BeforeBlocklist) {
-    aWriter.Write(Annotation::User32BeforeBlocklist, "1");
+    WriteAnnotation(file, Annotation::User32BeforeBlocklist, "1\n", &nBytes);
   }
 }
 
-using WriterFn = void (*)(AnnotationWriter&);
+using WriterFn = void (*)(HANDLE);
 static WriterFn gWriterFn = &InternalWriteNotes;
 
 static void GetNativeNtBlockSetWriter() {
   auto nativeWriter = reinterpret_cast<WriterFn>(
       ::GetProcAddress(::GetModuleHandleW(nullptr), "NativeNtBlockSet_Write"));
   if (nativeWriter) {
     gWriterFn = nativeWriter;
   }
 }
 
-MFBT_API void DllBlocklist_WriteNotes(AnnotationWriter& aWriter) {
+MFBT_API void DllBlocklist_WriteNotes(HANDLE file) {
   MOZ_ASSERT(gWriterFn);
-  gWriterFn(aWriter);
+  gWriterFn(file);
 }
 
 MFBT_API bool DllBlocklist_CheckStatus() {
   if (sBlocklistInitFailed || sUser32BeforeBlocklist) return false;
   return true;
 }
 
 // ============================================================================
--- a/mozglue/dllservices/WindowsDllBlocklist.h
+++ b/mozglue/dllservices/WindowsDllBlocklist.h
@@ -5,31 +5,30 @@
 
 #ifndef mozilla_windowsdllblocklist_h
 #define mozilla_windowsdllblocklist_h
 
 #if (defined(_MSC_VER) || defined(__MINGW32__)) && \
     (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64))
 
 #  include <windows.h>
-#  include "CrashAnnotations.h"
 #  include "mozilla/Attributes.h"
 #  include "mozilla/Types.h"
 
 #  define HAS_DLL_BLOCKLIST
 
 enum DllBlocklistInitFlags {
   eDllBlocklistInitFlagDefault = 0,
   eDllBlocklistInitFlagIsChildProcess = 1,
   eDllBlocklistInitFlagWasBootstrapped = 2
 };
 
 MFBT_API void DllBlocklist_Initialize(
     uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
-MFBT_API void DllBlocklist_WriteNotes(CrashReporter::AnnotationWriter& aWriter);
+MFBT_API void DllBlocklist_WriteNotes(HANDLE file);
 MFBT_API bool DllBlocklist_CheckStatus();
 
 // This export intends to clean up after DllBlocklist_Initialize().
 // It's disabled in release builds for performance and to limit callers' ability
 // to interfere with dll blocking.
 #  ifdef DEBUG
 MFBT_API void DllBlocklist_Shutdown();
 #  endif  // DEBUG
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -24,17 +24,16 @@ const { XPCOMUtils } = ChromeUtils.impor
 );
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { TestUtils } = ChromeUtils.import(
   "resource://testing-common/TestUtils.jsm"
 );
 const { ContentTask } = ChromeUtils.import(
   "resource://testing-common/ContentTask.jsm"
 );
-const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   ProtocolProxyService: [
@@ -1637,16 +1636,23 @@ var BrowserTestUtils = {
    */
   async crashFrame(
     browser,
     shouldShowTabCrashPage = true,
     shouldClearMinidumps = true,
     browsingContext
   ) {
     let extra = {};
+    let KeyValueParser = {};
+    if (AppConstants.MOZ_CRASHREPORTER) {
+      ChromeUtils.import(
+        "resource://gre/modules/KeyValueParser.jsm",
+        KeyValueParser
+      );
+    }
 
     if (!browser.isRemoteBrowser) {
       throw new Error("<xul:browser> needs to be remote in order to crash");
     }
 
     /**
      * Returns the directory where crash dumps are stored.
      *
@@ -1707,33 +1713,29 @@ var BrowserTestUtils = {
           }
         }
 
         let removalPromise = Promise.resolve();
 
         if (dumpID) {
           removalPromise = Services.crashmanager
             .ensureCrashIsPresent(dumpID)
-            .then(async () => {
+            .then(() => {
               let minidumpDirectory = getMinidumpDirectory();
               let extrafile = minidumpDirectory.clone();
               extrafile.append(dumpID + ".extra");
               if (extrafile.exists()) {
+                dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
                 if (AppConstants.MOZ_CRASHREPORTER) {
-                  let extradata = await OS.File.read(extrafile.path, {
-                    encoding: "utf-8",
-                  });
-                  extra = JSON.parse(extradata);
+                  extra = KeyValueParser.parseKeyValuePairsFromFile(extrafile);
                 } else {
                   dump(
                     "\nCrashReporter not enabled - will not return any extra data\n"
                   );
                 }
-              } else {
-                dump(`\nNo .extra file for dumpID: ${dumpID}\n`);
               }
 
               if (shouldClearMinidumps) {
                 removeFile(minidumpDirectory, dumpID + ".dmp");
                 removeFile(minidumpDirectory, dumpID + ".extra");
               }
             });
         }
--- a/testing/specialpowers/content/SpecialPowersParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersParent.jsm
@@ -5,33 +5,79 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["SpecialPowersParent"];
 
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   ExtensionData: "resource://gre/modules/Extension.jsm",
   ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
   PerTestCoverageUtils: "resource://testing-common/PerTestCoverageUtils.jsm",
   ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
   SpecialPowersSandbox: "resource://specialpowers/SpecialPowersSandbox.jsm",
   HiddenFrame: "resource://gre/modules/HiddenFrame.jsm",
 });
 
 class SpecialPowersError extends Error {
   get name() {
     return "SpecialPowersError";
   }
 }
 
+function parseKeyValuePairs(text) {
+  var lines = text.split("\n");
+  var data = {};
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i] == "") {
+      continue;
+    }
+
+    // can't just .split() because the value might contain = characters
+    let eq = lines[i].indexOf("=");
+    if (eq != -1) {
+      let [key, value] = [
+        lines[i].substring(0, eq),
+        lines[i].substring(eq + 1),
+      ];
+      if (key && value) {
+        data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
+      }
+    }
+  }
+  return data;
+}
+
+function parseKeyValuePairsFromFile(file) {
+  var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+    Ci.nsIFileInputStream
+  );
+  fstream.init(file, -1, 0, 0);
+  var is = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+    Ci.nsIConverterInputStream
+  );
+  is.init(
+    fstream,
+    "UTF-8",
+    1024,
+    Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER
+  );
+  var str = {};
+  var contents = "";
+  while (is.readString(4096, str) != 0) {
+    contents += str.value;
+  }
+  is.close();
+  fstream.close();
+  return parseKeyValuePairs(contents);
+}
+
 function getTestPlugin(pluginName) {
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   var tags = ph.getPluginTags();
   var name = pluginName || "Test Plug-in";
   for (var tag of tags) {
     if (tag.name == name) {
       return tag;
     }
@@ -229,19 +275,18 @@ class SpecialPowersParent extends JSWind
       case "plugin-crashed":
       case "ipc:content-shutdown":
         var message = { type: "crash-observed", dumpIDs: [] };
         aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
         if (aTopic == "plugin-crashed") {
           addDumpIDToMessage("pluginDumpID");
           addDumpIDToMessage("browserDumpID");
 
-          let self = this;
           let pluginID = aSubject.getPropertyAsAString("pluginDumpID");
-          let extra = self._getExtraData(pluginID);
+          let extra = this._getExtraData(pluginID);
           if (extra && "additional_minidumps" in extra) {
             let dumpNames = extra.additional_minidumps.split(",");
             for (let name of dumpNames) {
               message.dumpIDs.push({
                 id: pluginID + "-" + name,
                 extension: "dmp",
               });
             }
@@ -249,17 +294,16 @@ class SpecialPowersParent extends JSWind
         } else {
           // ipc:content-shutdown
           if (!aSubject.hasKey("abnormal")) {
             return; // This is a normal shutdown, ignore it
           }
 
           addDumpIDToMessage("dumpID");
         }
-
         this.sendAsyncMessage("SPProcessCrashService", message);
         break;
     }
   }
 
   _getCrashDumpDir() {
     if (!this._crashDumpDir) {
       this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
@@ -272,32 +316,23 @@ class SpecialPowersParent extends JSWind
     if (!this._pendingCrashDumpDir) {
       this._pendingCrashDumpDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
       this._pendingCrashDumpDir.append("Crash Reports");
       this._pendingCrashDumpDir.append("pending");
     }
     return this._pendingCrashDumpDir;
   }
 
-  async _getExtraData(dumpId) {
+  _getExtraData(dumpId) {
     let extraFile = this._getCrashDumpDir().clone();
     extraFile.append(dumpId + ".extra");
     if (!extraFile.exists()) {
       return null;
     }
-
-    var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
-      Ci.nsIFileInputStream
-    );
-    fstream.init(extraFile, -1, 0, 0);
-    let available = fstream.available();
-    let json = NetUtil.readInputStreamToString(fstream, available);
-    fstream.close();
-
-    return JSON.parse(json);
+    return parseKeyValuePairsFromFile(extraFile);
   }
 
   _deleteCrashDumpFiles(aFilenames) {
     var crashDumpDir = this._getCrashDumpDir();
     if (!crashDumpDir.exists()) {
       return false;
     }
 
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -1,16 +1,19 @@
 /* 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/. */
 
 "use strict";
 
 const myScope = this;
 
+const { parseKeyValuePairsFromLines } = ChromeUtils.import(
+  "resource://gre/modules/KeyValueParser.jsm"
+);
 ChromeUtils.import("resource://gre/modules/Log.jsm", this);
 ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
 const { PromiseUtils } = ChromeUtils.import(
   "resource://gre/modules/PromiseUtils.jsm"
 );
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 const { TelemetryController } = ChromeUtils.import(
   "resource://gre/modules/TelemetryController.jsm"
@@ -45,17 +48,20 @@ function dateToDays(date) {
  * @param field {String} The name of the field to be parsed and removed
  *
  * @returns {String} the field contents as a string, null if none was found
  */
 function getAndRemoveField(obj, field) {
   let value = null;
 
   if (field in obj) {
-    value = obj[field];
+    // We split extra files on LF characters but Windows-generated ones might
+    // contain trailing CR characters so trim them here.
+    value = obj[field].trim();
+
     delete obj[field];
   }
 
   return value;
 }
 
 /**
  * Parse the string stored in the specified field as JSON and then remove the
@@ -205,18 +211,16 @@ this.CrashManager.prototype = Object.fre
   // Number of days after which a crash with no activity will get purged.
   PURGE_OLDER_THAN_DAYS: 180,
 
   // 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 event is obsolete.
-  EVENT_FILE_ERROR_OBSOLETE: "obsolete",
   // The type of event is unknown.
   EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
 
   _lazyGetDir(field, path, leaf) {
     delete this[field];
     let value = OS.Path.join(path, leaf);
     Object.defineProperty(this, field, { value });
     return value;
@@ -344,17 +348,16 @@ this.CrashManager.prototype = Object.fre
             let result = await this._processEventFile(entry);
 
             switch (result) {
               case this.EVENT_FILE_SUCCESS:
                 needsSave = true;
               // Fall through.
 
               case this.EVENT_FILE_ERROR_MALFORMED:
-              case this.EVENT_FILE_ERROR_OBSOLETE:
                 deletePaths.push(entry.path);
                 break;
 
               case this.EVENT_FILE_ERROR_UNKNOWN_EVENT:
                 break;
 
               default:
                 Cu.reportError(
@@ -674,17 +677,17 @@ this.CrashManager.prototype = Object.fre
     // If we have a saved environment, use it. Otherwise report
     // the current environment.
     let reportMeta = Cu.cloneInto(metadata, myScope);
     let crashEnvironment = parseAndRemoveField(
       reportMeta,
       "TelemetryEnvironment"
     );
     let sessionId = getAndRemoveField(reportMeta, "TelemetrySessionId");
-    let stackTraces = getAndRemoveField(reportMeta, "StackTraces");
+    let stackTraces = parseAndRemoveField(reportMeta, "StackTraces");
     let minidumpSha256Hash = getAndRemoveField(
       reportMeta,
       "MinidumpSha256Hash"
     );
 
     // Filter the remaining annotations to remove privacy-sensitive ones
     reportMeta = this._filterAnnotations(reportMeta);
 
@@ -714,22 +717,26 @@ this.CrashManager.prototype = Object.fre
     // The payload types and formats are documented in docs/crash-events.rst.
     // Do not change the format of an existing type. Instead, invent a new
     // type.
     // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
     let lines = payload.split("\n");
 
     switch (type) {
       case "crash.main.1":
+        if (lines.length > 1) {
+          this._log.warn(
+            "Multiple lines unexpected in payload for " + entry.path
+          );
+          return this.EVENT_FILE_ERROR_MALFORMED;
+        }
+      // fall-through
       case "crash.main.2":
-        return this.EVENT_FILE_ERROR_OBSOLETE;
-
-      case "crash.main.3":
         let crashID = lines[0];
-        let metadata = JSON.parse(lines[1]);
+        let metadata = parseKeyValuePairsFromLines(lines.slice(1));
         store.addCrash(
           this.PROCESS_TYPE_MAIN,
           this.CRASH_TYPE_CRASH,
           crashID,
           date,
           metadata
         );
 
--- a/toolkit/components/crashes/CrashManagerTest.jsm
+++ b/toolkit/components/crashes/CrashManagerTest.jsm
@@ -96,20 +96,20 @@ this.TestingCrashManager.prototype = {
 
     return (async function() {
       let mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
       await OS.File.open(path, { create: true }, { unixMode: mode });
       dump("Create ignored dump file: " + path + "\n");
     })();
   },
 
-  createEventsFile(filename, type, date, id, content, index = 0) {
+  createEventsFile(filename, type, date, content, index = 0) {
     let path = OS.Path.join(this._eventsDirs[index], filename);
-    let dateInSecs = Math.floor(date.getTime() / 1000);
-    let data = type + "\n" + dateInSecs + "\n" + id + "\n" + content;
+
+    let data = type + "\n" + Math.floor(date.getTime() / 1000) + "\n" + content;
     let encoder = new TextEncoder();
     let array = encoder.encode(data);
 
     return (async function() {
       await OS.File.writeAtomic(path, array);
       await OS.File.setDates(path, date, date);
     })();
   },
--- a/toolkit/components/crashes/CrashService.jsm
+++ b/toolkit/components/crashes/CrashService.jsm
@@ -1,16 +1,19 @@
 /* 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/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
 ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm", this);
+const { parseKeyValuePairs } = ChromeUtils.import(
+  "resource://gre/modules/KeyValueParser.jsm"
+);
 ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 
 // Set to true if the application is quitting
 var gQuitting = false;
 
 // Tracks all the running instances of the minidump-analyzer
 var gRunningProcesses = new Set();
@@ -113,18 +116,29 @@ function computeMinidumpHash(minidumpPat
  * @return {Promise} A promise that resolves to an object holding the crash
  *         annotations.
  */
 function processExtraFile(extraPath) {
   return (async function() {
     try {
       let decoder = new TextDecoder();
       let extraData = await OS.File.read(extraPath);
+      let keyValuePairs = parseKeyValuePairs(decoder.decode(extraData));
 
-      return JSON.parse(decoder.decode(extraData));
+      // When reading from an .extra file literal '\\n' sequences are
+      // automatically unescaped to two backslashes plus a newline, so we need
+      // to re-escape them into '\\n' again so that the fields holding JSON
+      // strings are valid.
+      ["TelemetryEnvironment", "StackTraces"].forEach(field => {
+        if (field in keyValuePairs) {
+          keyValuePairs[field] = keyValuePairs[field].replace(/\n/g, "n");
+        }
+      });
+
+      return keyValuePairs;
     } catch (e) {
       Cu.reportError(e);
       return {};
     }
   })();
 }
 
 /**
--- a/toolkit/components/crashes/docs/crash-events.rst
+++ b/toolkit/components/crashes/docs/crash-events.rst
@@ -60,51 +60,36 @@ Each subsection documents the different 
 produced. Each section name corresponds to the first line of the crash
 event file.
 
 Currently only main process crashes produce event files. Because crashes and
 hangs in child processes can be easily recorded by the main process, we do not
 foresee the need for writing event files for child processes, design
 considerations below notwithstanding.
 
-crash.main.3
-^^^^^^^^^^^^
-
-This event is produced when the main process crashes.
-
-The payload of this event is delimited by UNIX newlines (*\n*) and contains the
-following fields:
-
-* The crash ID string, very likely a UUID
-* One line holding the crash metadata serialized as a JSON string
-
 crash.main.2
 ^^^^^^^^^^^^
 
 This event is produced when the main process crashes.
 
 The payload of this event is delimited by UNIX newlines (*\n*) and contains the
 following fields:
 
 * The crash ID string, very likely a UUID
 * 0 or more lines of metadata, each containing one key=value pair of text
 
-This event is obsolete.
-
 crash.main.1
 ^^^^^^^^^^^^
 
 This event is produced when the main process crashes.
 
 The payload of this event is the string crash ID, very likely a UUID.
 There should be ``UUID.dmp`` and ``UUID.extra`` files on disk, saved by
 Breakpad.
 
-This event is obsolete.
-
 crash.submission.1
 ^^^^^^^^^^^^^^^^^^
 
 This event is produced when a crash is submitted.
 
 The payload of this event is delimited by UNIX newlines (*\n*) and contains the
 following fields:
 
--- a/toolkit/components/crashes/tests/xpcshell/crash.extra
+++ b/toolkit/components/crashes/tests/xpcshell/crash.extra
@@ -1,1 +1,30 @@
-{"ContentSandboxLevel":"2","TelemetryEnvironment":"{\"EscapedField\":\"EscapedData\\n\\nfoo\"}","EMCheckCompatibility":"true","ProductName":"Firefox","ContentSandboxCapabilities":"119","TelemetryClientId":"","Vendor":"Mozilla","InstallTime":"1000000000","Theme":"classic/1.0","ReleaseChannel":"default","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":"","DOMIPCEnabled":"1","Add-ons":"","CrashTime":"1494582646","UptimeTS":"14.9179586","ThreadIdNameMapping":"","ContentSandboxEnabled":"1","ProcessType":"content","StartupTime":"1000000000","URL":"about:home"}
+ContentSandboxLevel=2
+TelemetryEnvironment={"EscapedField":"EscapedData\\n\\nfoo"}
+EMCheckCompatibility=true
+ProductName=Firefox
+ContentSandboxCapabilities=119
+TelemetryClientId=
+Vendor=Mozilla
+InstallTime=1000000000
+Theme=classic/1.0
+ReleaseChannel=default
+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=
+DOMIPCEnabled=1
+Add-ons=
+CrashTime=1494582646
+UptimeTS=14.9179586
+ThreadIdNameMapping=
+ContentSandboxLevel=2
+ContentSandboxEnabled=1
+ProcessType=content
+DOMIPCEnabled=1
+StartupTime=1000000000
+URL=about:home
+ContentSandboxCapabilities=119
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -117,19 +117,19 @@ add_task(async function test_store_expir
 
   await sleep(300);
   Assert.ok(!m._store, "Store has gone away.");
 });
 
 // Ensure discovery of unprocessed events files works.
 add_task(async function test_unprocessed_events_files() {
   let m = await getManager();
-  await m.createEventsFile("1", "test.1", new Date(), "foo", "{}", 0);
-  await m.createEventsFile("2", "test.1", new Date(), "bar", "{}", 0);
-  await m.createEventsFile("1", "test.1", new Date(), "baz", "{}", 1);
+  await m.createEventsFile("1", "test.1", new Date(), "foo", 0);
+  await m.createEventsFile("2", "test.1", new Date(), "bar", 0);
+  await m.createEventsFile("1", "test.1", new Date(), "baz", 1);
 
   let paths = await m._getUnprocessedEventsFiles();
   Assert.equal(paths.length, 3);
 });
 
 // Ensure only 1 aggregateEventsFiles() is allowed at a time.
 add_task(async function test_aggregate_events_locking() {
   let m = await getManager();
@@ -154,34 +154,34 @@ add_task(async function test_malformed_f
   count = await m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
 // Unknown event types should be ignored.
 add_task(async function test_aggregate_ignore_unknown_events() {
   let m = await getManager();
 
-  await m.createEventsFile("1", "crash.main.3", DUMMY_DATE, "id1", "{}");
+  await m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1");
   await m.createEventsFile("2", "foobar.1", new Date(), "dummy");
 
   let count = await m.aggregateEventsFiles();
   Assert.equal(count, 2);
 
   count = await m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   count = await m.aggregateEventsFiles();
   Assert.equal(count, 1);
 });
 
 add_task(async function test_prune_old() {
   let m = await getManager();
   let oldDate = new Date(Date.now() - 86400000);
   let newDate = new Date(Date.now() - 10000);
-  await m.createEventsFile("1", "crash.main.3", oldDate, "id1", "{}");
+  await m.createEventsFile("1", "crash.main.2", oldDate, "id1");
   await m.addCrash(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_CRASH, "id2", newDate);
 
   await m.aggregateEventsFiles();
 
   let crashes = await m.getCrashes();
   Assert.equal(crashes.length, 2);
 
   await m.pruneOldCrashes(new Date(oldDate.getTime() + 10000));
@@ -196,65 +196,72 @@ add_task(async function test_prune_old()
   // don't have same guarantees as JS dates.
   await m.pruneOldCrashes(new Date(newDate.getTime() + 5000));
   crashes = await m.getCrashes();
   Assert.equal(crashes.length, 0);
 });
 
 add_task(async function test_schedule_maintenance() {
   let m = await getManager();
-  await m.createEventsFile("1", "crash.main.3", DUMMY_DATE, "id1", "{}");
+  await m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "id1");
 
   let oldDate = new Date(
     Date.now() - m.PURGE_OLDER_THAN_DAYS * 2 * 24 * 60 * 60 * 1000
   );
-  await m.createEventsFile("2", "crash.main.3", oldDate, "id2", "{}");
+  await m.createEventsFile("2", "crash.main.2", oldDate, "id2");
 
   await m.scheduleMaintenance(25);
   let crashes = await m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, "id1");
 });
 
 const crashId = "3cb67eba-0dc7-6f78-6a569a0e-172287ec";
 const crashPingUuid = "103dbdf2-339b-4b9c-a7cc-5f9506ea9d08";
 const productName = "Firefox";
 const productId = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
 const sha256Hash =
   "f8410c3ac4496cfa9191a1240f0e365101aef40c7bf34fc5bcb8ec511832ed79";
-const stackTraces = { status: "OK" };
+const stackTraces = '{"status":"OK"}';
 
 add_task(async function test_main_crash_event_file() {
   let ac = new TelemetryArchiveTesting.Checker();
   await ac.promiseInit();
   let theEnvironment = TelemetryEnvironment.currentEnvironment;
   const sessionId = "be66af2f-2ee5-4330-ae95-44462dfbdf0c";
 
   // To test proper escaping, add data to the environment with an embedded
   // double-quote
   theEnvironment.testValue = 'MyValue"';
 
   let m = await getManager();
-  const metadata = JSON.stringify({
-    ProductName: productName,
-    ProductID: productId,
-    TelemetryEnvironment: JSON.stringify(theEnvironment),
-    TelemetrySessionId: sessionId,
-    MinidumpSha256Hash: sha256Hash,
-    StackTraces: stackTraces,
-    ThisShouldNot: "end-up-in-the-ping",
-  });
+  const fileContent =
+    crashId +
+    "\n" +
+    "ProductName=" +
+    productName +
+    "\n" +
+    "ProductID=" +
+    productId +
+    "\n" +
+    "TelemetryEnvironment=" +
+    JSON.stringify(theEnvironment) +
+    "\n" +
+    "TelemetrySessionId=" +
+    sessionId +
+    "\n" +
+    "MinidumpSha256Hash=" +
+    sha256Hash +
+    "\n" +
+    "StackTraces=" +
+    stackTraces +
+    "\n" +
+    "ThisShouldNot=end-up-in-the-ping\n";
 
-  await m.createEventsFile(
-    crashId,
-    "crash.main.3",
-    DUMMY_DATE,
-    crashId,
-    metadata
-  );
+  await m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
   let count = await m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   let crashes = await m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, crashId);
   Assert.equal(crashes[0].type, "main-crash");
   Assert.equal(crashes[0].metadata.ProductName, productName);
@@ -288,29 +295,28 @@ add_task(async function test_main_crash_
 
   count = await m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
 add_task(async function test_main_crash_event_file_noenv() {
   let ac = new TelemetryArchiveTesting.Checker();
   await ac.promiseInit();
-  const metadata = JSON.stringify({
-    ProductName: productName,
-    ProductID: productId,
-  });
+  const fileContent =
+    crashId +
+    "\n" +
+    "ProductName=" +
+    productName +
+    "\n" +
+    "ProductID=" +
+    productId +
+    "\n";
 
   let m = await getManager();
-  await m.createEventsFile(
-    crashId,
-    "crash.main.3",
-    DUMMY_DATE,
-    crashId,
-    metadata
-  );
+  await m.createEventsFile(crashId, "crash.main.2", DUMMY_DATE, fileContent);
   let count = await m.aggregateEventsFiles();
   Assert.equal(count, 1);
 
   let crashes = await m.getCrashes();
   Assert.equal(crashes.length, 1);
   Assert.equal(crashes[0].id, crashId);
   Assert.equal(crashes[0].type, "main-crash");
   Assert.deepEqual(crashes[0].metadata, {
@@ -328,34 +334,32 @@ add_task(async function test_main_crash_
   Assert.ok(found.environment, "There is an environment");
 
   count = await m.aggregateEventsFiles();
   Assert.equal(count, 0);
 });
 
 add_task(async function test_crash_submission_event_file() {
   let m = await getManager();
-  await m.createEventsFile("1", "crash.main.3", DUMMY_DATE, "crash1", "{}");
+  await m.createEventsFile("1", "crash.main.2", DUMMY_DATE, "crash1");
   await m.createEventsFile(
     "1-submission",
     "crash.submission.1",
     DUMMY_DATE_2,
-    "crash1",
-    "false\n"
+    "crash1\nfalse\n"
   );
 
   // The line below has been intentionally commented out to make sure that
   // the crash record is created when one does not exist.
   // yield m.createEventsFile("2", "crash.main.1", DUMMY_DATE, "crash2");
   await m.createEventsFile(
     "2-submission",
     "crash.submission.1",
     DUMMY_DATE_2,
-    "crash2",
-    "true\nbp-2"
+    "crash2\ntrue\nbp-2"
   );
   let count = await m.aggregateEventsFiles();
   Assert.equal(count, 3);
 
   let crashes = await m.getCrashes();
   Assert.equal(crashes.length, 2);
 
   let map = new Map(crashes.map(crash => [crash.id, crash]));
@@ -393,23 +397,17 @@ add_task(async function test_multiline_c
 
 // Main process crashes should be remembered beyond the high water mark.
 add_task(async function test_high_water_mark() {
   let m = await getManager();
 
   let store = await m._getStore();
 
   for (let i = 0; i < store.HIGH_WATER_DAILY_THRESHOLD + 1; i++) {
-    await m.createEventsFile(
-      "m" + i,
-      "crash.main.3",
-      DUMMY_DATE,
-      "m" + i,
-      "{}"
-    );
+    await m.createEventsFile("m" + i, "crash.main.2", DUMMY_DATE, "m" + i);
   }
 
   let count = await m.aggregateEventsFiles();
   Assert.equal(count, CrashStore.prototype.HIGH_WATER_DAILY_THRESHOLD + 1);
 
   // Need to fetch again in case the first one was garbage collected.
   store = await m._getStore();
 
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_service.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_service.js
@@ -84,17 +84,17 @@ async function test_addCrashBase(crashId
   Assert.equal(crash.metadata.ProcessType, "content");
   Assert.equal(
     crash.metadata.MinidumpSha256Hash,
     "c8ad56a2096310f40c8a4b46c890625a740fdd72e409f412933011ff947c5a40"
   );
   Assert.ok(crash.metadata.StackTraces, "The StackTraces field is present.\n");
 
   try {
-    let stackTraces = crash.metadata.StackTraces;
+    let stackTraces = JSON.parse(crash.metadata.StackTraces);
     Assert.equal(stackTraces.status, "OK");
     Assert.ok(stackTraces.crash_info, "The crash_info field is populated.");
     Assert.ok(
       stackTraces.modules && !!stackTraces.modules.length,
       "The module list is populated."
     );
     Assert.ok(
       stackTraces.threads && !!stackTraces.threads.length,
@@ -120,17 +120,17 @@ async function test_addCrashBase(crashId
   }
 
   try {
     let telemetryEnvironment = JSON.parse(crash.metadata.TelemetryEnvironment);
     Assert.equal(telemetryEnvironment.EscapedField, "EscapedData\n\nfoo");
   } catch (e) {
     Assert.ok(
       false,
-      "TelemetryEnvironment contents were not properly escaped\n"
+      "TelemetryEnvironment contents were not properly re-escaped\n"
     );
   }
 
   await teardown();
 }
 
 add_task(async function test_addCrash() {
   await test_addCrashBase("56cd87bc-bb26-339b-3a8e-f00c0f11380e", false);
--- a/toolkit/crashreporter/CrashAnnotations.h.in
+++ b/toolkit/crashreporter/CrashAnnotations.h.in
@@ -1,16 +1,15 @@
 /* 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 <cstddef>
 #include <cstdint>
 
 namespace CrashReporter {
 
 // Typed enum representing all crash annotations
 enum class Annotation : uint32_t {
 ${enum}
 };
@@ -67,20 +66,11 @@ bool IsAnnotationWhitelistedForPing(Anno
  * 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);
 
-/**
- * Abstract annotation writer, this is needed only for code that writes out
- * annotations in the exception handler.
- */
-class AnnotationWriter {
- public:
-  virtual void Write(Annotation aAnnotation, const char* aValue, size_t aLen = 0) = 0;
-};
-
 } // namespace CrashReporter
 
 #endif // CrashAnnotations_h
--- a/toolkit/crashreporter/CrashSubmit.jsm
+++ b/toolkit/crashreporter/CrashSubmit.jsm
@@ -7,32 +7,35 @@ const { FileUtils } = ChromeUtils.import
   "resource://gre/modules/FileUtils.jsm"
 );
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 const { AppConstants } = ChromeUtils.import(
   "resource://gre/modules/AppConstants.jsm"
 );
+const {
+  parseKeyValuePairs,
+  parseKeyValuePairsFromFileAsync,
+} = ChromeUtils.import("resource://gre/modules/KeyValueParser.jsm");
 XPCOMUtils.defineLazyGlobalGetters(this, [
   "File",
   "FormData",
   "XMLHttpRequest",
 ]);
 
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 var EXPORTED_SYMBOLS = ["CrashSubmit"];
 
 const SUCCESS = "success";
 const FAILED = "failed";
 const SUBMITTING = "submitting";
 
 const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
-const SUBMISSION_REGEX = /^bp-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
 
 // TODO: this is still synchronous; need an async INI parser to make it async
 function parseINIStrings(path) {
   let file = new FileUtils.File(path);
   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
     Ci.nsIINIParserFactory
   );
   let parser = factory.createINIParser(file);
@@ -121,33 +124,44 @@ function getPendingMinidump(id) {
   let pendingDir = getDir("pending");
 
   return [".dmp", ".extra", ".memory.json.gz"].map(suffix => {
     return OS.Path.join(pendingDir, `${id}${suffix}`);
   });
 }
 
 async function synthesizeExtraFile(extra) {
-  let url = "https://crash-reports.mozilla.com/submit?id=";
-  Services.appinfo.ID +
+  let data =
+    "ServerURL=https://crash-reports.mozilla.com/submit?id=" +
+    Services.appinfo.ID +
     "&version=" +
     Services.appinfo.version +
     "&buildid=" +
-    Services.appinfo.appBuildID;
-  let data = {
-    ServerURL: url,
-    Vendor: Services.appinfo.vendor,
-    ProductName: Services.appinfo.name,
-    ProductID: Services.appinfo.ID,
-    Version: Services.appinfo.version,
-    BuildID: Services.appinfo.appBuildID,
-    ReleaseChannel: AppConstants.MOZ_UPDATE_CHANNEL,
-  };
+    Services.appinfo.appBuildID +
+    "\n" +
+    "Vendor=" +
+    Services.appinfo.vendor +
+    "\n" +
+    "ProductName=" +
+    Services.appinfo.name +
+    "\n" +
+    "ProductID=" +
+    Services.appinfo.ID +
+    "\n" +
+    "Version=" +
+    Services.appinfo.version +
+    "\n" +
+    "BuildID=" +
+    Services.appinfo.appBuildID +
+    "\n" +
+    "ReleaseChannel=" +
+    AppConstants.MOZ_UPDATE_CHANNEL +
+    "\n";
 
-  await OS.File.writeAtomic(extra, JSON.stringify(data), { encoding: "utf-8" });
+  await OS.File.writeAtomic(extra, data, { encoding: "utf-8" });
 }
 
 async function writeSubmittedReportAsync(crashID, viewURL) {
   let strings = await getL10nStrings();
   let data = strings.crashid.replace("%s", crashID);
 
   if (viewURL) {
     data += "\n" + strings.reporturl.replace("%s", viewURL);
@@ -208,34 +222,16 @@ Submitter.prototype = {
     this.additionalDumps = null;
     // remove this object from the list of active submissions
     let idx = CrashSubmit._activeSubmissions.indexOf(this);
     if (idx != -1) {
       CrashSubmit._activeSubmissions.splice(idx, 1);
     }
   },
 
-  parseResponse: function Submitter_parseResponse(response) {
-    let parsedResponse = {};
-
-    for (let line of response.split("\n")) {
-      let data = line.split("=");
-
-      if (
-        (data.length == 2 &&
-          (data[0] == "CrashID" && SUBMISSION_REGEX.test(data[1]))) ||
-        data[0] == "ViewURL"
-      ) {
-        parsedResponse[data[0]] = data[1];
-      }
-    }
-
-    return parsedResponse;
-  },
-
   submitForm: function Submitter_submitForm() {
     if (!("ServerURL" in this.extraKeyVals)) {
       return false;
     }
     let serverURL = this.extraKeyVals.ServerURL;
 
     // Override the submission URL from the environment
 
@@ -247,29 +243,25 @@ Submitter.prototype = {
     }
 
     let xhr = new XMLHttpRequest();
     xhr.open("POST", serverURL, true);
 
     let formData = new FormData();
 
     // add the data
-    delete this.extraKeyVals.ServerURL;
-    delete this.extraKeyVals.StackTraces;
-
-    let payload = Object.assign({}, this.extraKeyVals);
+    for (let [name, value] of Object.entries(this.extraKeyVals)) {
+      if (name != "ServerURL" && name != "StackTraces") {
+        formData.append(name, value);
+      }
+    }
     if (this.noThrottle) {
       // tell the server not to throttle this, since it was manually submitted
-      payload.Throttleable = "0";
+      formData.append("Throttleable", "0");
     }
-    let json = new Blob([JSON.stringify(payload)], {
-      type: "application/json",
-    });
-    formData.append("extra", json);
-
     // add the minidumps
     let promises = [
       File.createFromFileName(this.dump).then(file => {
         formData.append("upload_file_minidump", file);
       }),
     ];
 
     if (this.memory) {
@@ -293,17 +285,17 @@ Submitter.prototype = {
     }
 
     let manager = Services.crashmanager;
     let submissionID = manager.generateSubmissionID();
 
     xhr.addEventListener("readystatechange", evt => {
       if (xhr.readyState == 4) {
         let ret =
-          xhr.status === 200 ? this.parseResponse(xhr.responseText) : {};
+          xhr.status === 200 ? parseKeyValuePairs(xhr.responseText) : {};
         let submitted = !!ret.CrashID;
         let p = Promise.resolve();
 
         if (this.recordSubmission) {
           let result = submitted
             ? manager.SUBMISSION_RESULT_OK
             : manager.SUBMISSION_RESULT_FAILED;
           p = manager.addSubmissionResult(
@@ -394,19 +386,17 @@ Submitter.prototype = {
     if (!extraExists) {
       await synthesizeExtraFile(extra);
     }
 
     this.dump = dump;
     this.extra = extra;
     this.memory = memoryExists ? memory : null;
 
-    let decoder = new TextDecoder();
-    let extraData = await OS.File.read(extra);
-    let extraKeyVals = JSON.parse(decoder.decode(extraData));
+    let extraKeyVals = await parseKeyValuePairsFromFileAsync(extra);
 
     for (let key in extraKeyVals) {
       if (!(key in this.extraKeyVals)) {
         this.extraKeyVals[key] = extraKeyVals[key];
       }
     }
 
     let additionalDumps = [];
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/KeyValueParser.jsm
@@ -0,0 +1,66 @@
+/* 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/. */
+
+ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+var EXPORTED_SYMBOLS = [
+  "parseKeyValuePairsFromLines",
+  "parseKeyValuePairs",
+  "parseKeyValuePairsFromFile",
+  "parseKeyValuePairsFromFileAsync",
+];
+
+var parseKeyValuePairsFromLines = function(lines) {
+  let data = {};
+  for (let line of lines) {
+    if (line == "") {
+      continue;
+    }
+
+    // can't just .split() because the value might contain = characters
+    let eq = line.indexOf("=");
+    if (eq != -1) {
+      let [key, value] = [line.substring(0, eq), line.substring(eq + 1)];
+      if (key && value) {
+        data[key] = value.replace(/\\n/g, "\n").replace(/\\\\/g, "\\");
+      }
+    }
+  }
+  return data;
+};
+
+function parseKeyValuePairs(text) {
+  let lines = text.split("\n");
+  return parseKeyValuePairsFromLines(lines);
+}
+
+// some test setup still uses this sync version
+function parseKeyValuePairsFromFile(file) {
+  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+    Ci.nsIFileInputStream
+  );
+  fstream.init(file, -1, 0, 0);
+  let is = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+    Ci.nsIConverterInputStream
+  );
+  is.init(
+    fstream,
+    "UTF-8",
+    1024,
+    Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER
+  );
+  let str = {};
+  let contents = "";
+  while (is.readString(4096, str) != 0) {
+    contents += str.value;
+  }
+  is.close();
+  fstream.close();
+  return parseKeyValuePairs(contents);
+}
+
+async function parseKeyValuePairsFromFileAsync(file) {
+  let contents = await OS.File.read(file, { encoding: "utf-8" });
+  return parseKeyValuePairs(contents);
+}
--- a/toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.cc
+++ b/toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.cc
@@ -23,71 +23,74 @@
 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 // Disable exception handler warnings.
-#pragma warning(disable : 4530)
+#pragma warning( disable : 4530 )
 
 #include <errno.h>
 
 #include "windows/sender/crash_report_sender.h"
 #include "common/windows/http_upload.h"
 
 #if _MSC_VER < 1400  // MSVC 2005/8
 // Older MSVC doesn't have fscanf_s, but they are compatible as long as
 // we don't use the string conversions (%s/%c/%S/%C).
-#  define fscanf_s fscanf
+#define fscanf_s fscanf
 #endif
 
 namespace google_breakpad {
 
 static const char kCheckpointSignature[] = "GBP1\n";
 
-CrashReportSender::CrashReportSender(const wstring& checkpoint_file)
+CrashReportSender::CrashReportSender(const wstring &checkpoint_file)
     : checkpoint_file_(checkpoint_file),
       max_reports_per_day_(-1),
       last_sent_date_(-1),
       reports_sent_(0) {
-  FILE* fd;
+  FILE *fd;
   if (OpenCheckpointFile(L"r", &fd) == 0) {
     ReadCheckpoint(fd);
     fclose(fd);
   }
 }
 
 ReportResult CrashReportSender::SendCrashReport(
-    const wstring& url, const string& parameters,
-    const map<wstring, wstring>& files, wstring* report_code) {
+    const wstring &url, const map<wstring, wstring> &parameters,
+    const map<wstring, wstring> &files, wstring *report_code) {
   int today = GetCurrentDate();
-  if (today == last_sent_date_ && max_reports_per_day_ != -1 &&
+  if (today == last_sent_date_ &&
+      max_reports_per_day_ != -1 &&
       reports_sent_ >= max_reports_per_day_) {
     return RESULT_THROTTLED;
   }
 
   int http_response = 0;
-  bool result = HTTPUpload::SendRequest(url, parameters, files, NULL,
-                                        report_code, &http_response);
+  bool result = HTTPUpload::SendRequest(
+    url, parameters, files, NULL, report_code,
+    &http_response);
 
   if (result) {
     ReportSent(today);
     return RESULT_SUCCEEDED;
   } else if (http_response >= 400 && http_response < 500) {
     return RESULT_REJECTED;
   } else {
     return RESULT_FAILED;
   }
 }
 
-void CrashReportSender::ReadCheckpoint(FILE* fd) {
+void CrashReportSender::ReadCheckpoint(FILE *fd) {
   char buf[128];
-  if (!fgets(buf, sizeof(buf), fd) || strcmp(buf, kCheckpointSignature) != 0) {
+  if (!fgets(buf, sizeof(buf), fd) ||
+      strcmp(buf, kCheckpointSignature) != 0) {
     return;
   }
 
   if (fscanf_s(fd, "%d\n", &last_sent_date_) != 1) {
     last_sent_date_ = -1;
     return;
   }
   if (fscanf_s(fd, "%d\n", &reports_sent_) != 1) {
@@ -100,33 +103,33 @@ void CrashReportSender::ReportSent(int t
   // Update the report stats
   if (today != last_sent_date_) {
     last_sent_date_ = today;
     reports_sent_ = 0;
   }
   ++reports_sent_;
 
   // Update the checkpoint file
-  FILE* fd;
+  FILE *fd;
   if (OpenCheckpointFile(L"w", &fd) == 0) {
     fputs(kCheckpointSignature, fd);
     fprintf(fd, "%d\n", last_sent_date_);
     fprintf(fd, "%d\n", reports_sent_);
     fclose(fd);
   }
 }
 
 int CrashReportSender::GetCurrentDate() const {
   SYSTEMTIME system_time;
   GetSystemTime(&system_time);
   return (system_time.wYear * 10000) + (system_time.wMonth * 100) +
-         system_time.wDay;
+      system_time.wDay;
 }
 
-int CrashReportSender::OpenCheckpointFile(const wchar_t* mode, FILE** fd) {
+int CrashReportSender::OpenCheckpointFile(const wchar_t *mode, FILE **fd) {
   if (checkpoint_file_.empty()) {
     return ENOENT;
   }
 #if _MSC_VER >= 1400  // MSVC 2005/8
   return _wfopen_s(fd, checkpoint_file_.c_str(), mode);
 #else
   *fd = _wfopen(checkpoint_file_.c_str(), mode);
   if (*fd == NULL) {
--- a/toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.h
+++ b/toolkit/crashreporter/breakpad-client/windows/sender/crash_report_sender.h
@@ -33,90 +33,93 @@
 // CrashReportSender is a "static" class which provides an API to upload
 // crash reports via HTTP(S).  A crash report is formatted as a multipart POST
 // request, which contains a set of caller-supplied string key/value pairs,
 // and a minidump file to upload.
 //
 // To use this library in your project, you will need to link against
 // wininet.lib.
 
-#pragma warning(push)
+#pragma warning( push )
 // Disable exception handler warnings.
-#pragma warning(disable : 4530)
+#pragma warning( disable : 4530 )
 
 #include <map>
 #include <string>
 
 namespace google_breakpad {
 
+using std::wstring;
 using std::map;
-using std::string;
-using std::wstring;
 
 typedef enum {
   RESULT_FAILED = 0,  // Failed to communicate with the server; try later.
   RESULT_REJECTED,    // Successfully sent the crash report, but the
                       // server rejected it; don't resend this report.
   RESULT_SUCCEEDED,   // The server accepted the crash report.
   RESULT_THROTTLED    // No attempt was made to send the crash report, because
                       // we exceeded the maximum reports per day.
 } ReportResult;
 
 class CrashReportSender {
  public:
   // Initializes a CrashReportSender instance.
   // If checkpoint_file is non-empty, breakpad will persist crash report
   // state to this file.  A checkpoint file is required for
   // set_max_reports_per_day() to function properly.
-  explicit CrashReportSender(const wstring& checkpoint_file);
+  explicit CrashReportSender(const wstring &checkpoint_file);
   ~CrashReportSender() {}
 
   // Sets the maximum number of crash reports that will be sent in a 24-hour
   // period.  This uses the state persisted to the checkpoint file.
   // The default value of -1 means that there is no limit on reports sent.
-  void set_max_reports_per_day(int reports) { max_reports_per_day_ = reports; }
+  void set_max_reports_per_day(int reports) {
+    max_reports_per_day_ = reports;
+  }
 
   int max_reports_per_day() const { return max_reports_per_day_; }
 
   // Sends the specified files, along with the map of
   // name value pairs, as a multipart POST request to the given URL.
-  // Parameters are specified as a JSON-encoded string in |parameters|.
+  // Parameter names must contain only printable ASCII characters,
+  // and may not contain a quote (") character.
   // Only HTTP(S) URLs are currently supported.  The return value indicates
   // the result of the operation (see above for possible results).
   // If report_code is non-NULL and the report is sent successfully (that is,
   // the return value is RESULT_SUCCEEDED), a code uniquely identifying the
   // report will be returned in report_code.
   // (Otherwise, report_code will be unchanged.)
-  ReportResult SendCrashReport(const wstring& url, const string& parameters,
-                               const map<wstring, wstring>& files,
-                               wstring* report_code);
+  ReportResult SendCrashReport(const wstring &url,
+                               const map<wstring, wstring> &parameters,
+                               const map<wstring, wstring> &files,
+                               wstring *report_code);
 
  private:
   // Reads persistent state from a checkpoint file.
-  void ReadCheckpoint(FILE* fd);
+  void ReadCheckpoint(FILE *fd);
 
   // Called when a new report has been sent, to update the checkpoint state.
   void ReportSent(int today);
 
   // Returns today's date (UTC) formatted as YYYYMMDD.
   int GetCurrentDate() const;
 
   // Opens the checkpoint file with the specified mode.
   // Returns zero on success, or an error code on failure.
-  int OpenCheckpointFile(const wchar_t* mode, FILE** fd);
+  int OpenCheckpointFile(const wchar_t *mode, FILE **fd);
 
   wstring checkpoint_file_;
   int max_reports_per_day_;
   // The last date on which we sent a report, expressed as YYYYMMDD.
   int last_sent_date_;
   // Number of reports sent on last_sent_date_
   int reports_sent_;
 
   // Disallow copy constructor and operator=
-  explicit CrashReportSender(const CrashReportSender&);
-  void operator=(const CrashReportSender&);
+  explicit CrashReportSender(const CrashReportSender &);
+  void operator=(const CrashReportSender &);
 };
 
 }  // namespace google_breakpad
 
-#pragma warning(pop)
+#pragma warning( pop )
 
 #endif  // CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__
deleted file mode 100644
--- a/toolkit/crashreporter/breakpad-patches/10-json-upload.patch
+++ /dev/null
@@ -1,548 +0,0 @@
-changeset:   562948:7a8aa1cef9d3
-parent:      562940:36dc32ab3d6e
-user:        Gabriele Svelto <gsvelto@mozilla.com>
-date:        Fri Jul 05 21:46:17 2019 +0200
-summary:     Bug 1420363 - Platform-specific fixes; r=froydnj
-
-diff --git a/src/common/mac/HTTPMultipartUpload.h b/src/common/mac/HTTPMultipartUpload.h
---- a/src/common/mac/HTTPMultipartUpload.h
-+++ b/src/common/mac/HTTPMultipartUpload.h
-@@ -32,28 +32,28 @@
- // Each file is sent with a name field in addition to the filename and data
- // The data will be sent synchronously.
- 
- #import <Foundation/Foundation.h>
- 
- @interface HTTPMultipartUpload : NSObject {
-  @protected
-   NSURL *url_;                  // The destination URL (STRONG)
--  NSDictionary *parameters_;    // The key/value pairs for sending data (STRONG)
-+  NSMutableString *parameters_;  // The JSON payload for sending data (STRONG)
-   NSMutableDictionary *files_;  // Dictionary of name/file-path (STRONG)
-   NSString *boundary_;          // The boundary string (STRONG)
-   NSHTTPURLResponse *response_; // The response from the send (STRONG)
- }
- 
- - (id)initWithURL:(NSURL *)url;
- 
- - (NSURL *)URL;
- 
--- (void)setParameters:(NSDictionary *)parameters;
--- (NSDictionary *)parameters;
-+- (void)setParameters:(NSMutableString *)parameters;
-+- (NSMutableString *)parameters;
- 
- - (void)addFileAtPath:(NSString *)path name:(NSString *)name;
- - (void)addFileContents:(NSData *)data name:(NSString *)name;
- - (NSDictionary *)files;
- 
- // Set the data and return the response
- - (NSData *)send:(NSError **)error;
- - (NSHTTPURLResponse *)response;
-diff --git a/src/common/mac/HTTPMultipartUpload.m b/src/common/mac/HTTPMultipartUpload.m
---- a/src/common/mac/HTTPMultipartUpload.m
-+++ b/src/common/mac/HTTPMultipartUpload.m
-@@ -88,17 +88,17 @@ static NSData *SendSynchronousNSURLReque
-                                returningResponse:out_response
-                                            error:out_error];
- #endif
- }
- @interface HTTPMultipartUpload(PrivateMethods)
- - (NSString *)multipartBoundary;
- // Each of the following methods will append the starting multipart boundary,
- // but not the ending one.
--- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value;
-+- (NSData *)formDataForJSON:(NSString *)json;
- - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name;
- - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name;
- @end
- 
- @implementation HTTPMultipartUpload
- //=============================================================================
- #pragma mark -
- #pragma mark || Private ||
-@@ -105,23 +105,26 @@ static NSData *SendSynchronousNSURLReque
- //=============================================================================
- - (NSString *)multipartBoundary {
-   // The boundary has 27 '-' characters followed by 16 hex digits
-   return [NSString stringWithFormat:@"---------------------------%08X%08X",
-     rand(), rand()];
- }
- 
- //=============================================================================
--- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value {
--  NSString *escaped = PercentEncodeNSString(key);
--  NSString *fmt =
--    @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n";
--  NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value];
-+- (NSData *)formDataForJSON:(NSString *)json {
-+  NSMutableData *data = [NSMutableData data];
-+  NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"extra\"; "
-+                   "filename=\"extra.json\"\r\nContent-Type: application/json\r\n\r\n";
-+  NSString *form = [NSString stringWithFormat:fmt, boundary_];
- 
--  return [form dataUsingEncoding:NSUTF8StringEncoding];
-+  [data appendData:[form dataUsingEncoding:NSUTF8StringEncoding]];
-+  [data appendData:[json dataUsingEncoding:NSUTF8StringEncoding]];
-+
-+  return data;
- }
- 
- //=============================================================================
- - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name {
-   NSMutableData *data = [NSMutableData data];
-   NSString *escaped = PercentEncodeNSString(name);
-   NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; "
-     "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n";
-@@ -166,25 +169,25 @@ static NSData *SendSynchronousNSURLReque
- }
- 
- //=============================================================================
- - (NSURL *)URL {
-   return url_;
- }
- 
- //=============================================================================
--- (void)setParameters:(NSDictionary *)parameters {
-+- (void)setParameters:(NSMutableString *)parameters {
-   if (parameters != parameters_) {
-     [parameters_ release];
--    parameters_ = [parameters copy];
-+    parameters_ = [parameters mutableCopy];
-   }
- }
- 
- //=============================================================================
--- (NSDictionary *)parameters {
-+- (NSMutableString *)parameters {
-   return parameters_;
- }
- 
- //=============================================================================
- - (void)addFileAtPath:(NSString *)path name:(NSString *)name {
-   [files_ setObject:path forKey:name];
- }
- 
-@@ -205,26 +208,18 @@ static NSData *SendSynchronousNSURLReque
-           initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy
-       timeoutInterval:10.0 ];
- 
-   NSMutableData *postBody = [NSMutableData data];
- 
-   [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",
-     boundary_] forHTTPHeaderField:@"Content-type"];
- 
--  // Add any parameters to the message
--  NSArray *parameterKeys = [parameters_ allKeys];
--  NSString *key;
--
--  NSInteger count = [parameterKeys count];
--  for (NSInteger i = 0; i < count; ++i) {
--    key = [parameterKeys objectAtIndex:i];
--    [postBody appendData:[self formDataForKey:key
--                                        value:[parameters_ objectForKey:key]]];
--  }
-+  // Add JSON parameters to the message
-+  [postBody appendData:[self formDataForJSON:parameters_]];
- 
-   // Add any files to the message
-   NSArray *fileNames = [files_ allKeys];
-   for (NSString *name in fileNames) {
-     id fileOrData = [files_ objectForKey:name];
-     NSData *fileData;
- 
-     // The object can be either the path to a file (NSString) or the contents
-
-changeset:   562940:36dc32ab3d6e
-parent:      562937:9987d601c5eb
-user:        Gabriele Svelto <gsvelto@mozilla.com>
-date:        Wed Jul 03 14:20:58 2019 +0200
-summary:     Bug 1420363 - Windows-specific fixes; r=froydnj
-
-diff --git a/src/common/windows/http_upload.cc b/src/common/windows/http_upload.cc
---- a/src/common/windows/http_upload.cc
-+++ b/src/common/windows/http_upload.cc
-@@ -58,30 +58,25 @@ class HTTPUpload::AutoInternetHandle {
-   HINTERNET get() { return handle_; }
- 
-  private:
-   HINTERNET handle_;
- };
- 
- // static
- bool HTTPUpload::SendRequest(const wstring &url,
--                             const map<wstring, wstring> &parameters,
-+                             const string &parameters,
-                              const map<wstring, wstring> &files,
-                              int *timeout,
-                              wstring *response_body,
-                              int *response_code) {
-   if (response_code) {
-     *response_code = 0;
-   }
- 
--  // TODO(bryner): support non-ASCII parameter names
--  if (!CheckParameters(parameters)) {
--    return false;
--  }
--
-   // Break up the URL and make sure we can handle it
-   wchar_t scheme[16], host[256], path[256];
-   URL_COMPONENTS components;
-   memset(&components, 0, sizeof(components));
-   components.dwStructSize = sizeof(components);
-   components.lpszScheme = scheme;
-   components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]);
-   components.lpszHostName = host;
-@@ -260,35 +255,40 @@ wstring HTTPUpload::GenerateMultipartBou
- // static
- wstring HTTPUpload::GenerateRequestHeader(const wstring &boundary) {
-   wstring header = L"Content-Type: multipart/form-data; boundary=";
-   header += boundary;
-   return header;
- }
- 
- // static
--bool HTTPUpload::GenerateRequestBody(const map<wstring, wstring> &parameters,
-+bool HTTPUpload::GenerateRequestBody(const string &parameters,
-                                      const map<wstring, wstring> &files,
-                                      const wstring &boundary,
-                                      string *request_body) {
-   string boundary_str = WideToUTF8(boundary);
-   if (boundary_str.empty()) {
-     return false;
-   }
- 
-   request_body->clear();
- 
--  // Append each of the parameter pairs as a form-data part
--  for (map<wstring, wstring>::const_iterator pos = parameters.begin();
--       pos != parameters.end(); ++pos) {
--    request_body->append("--" + boundary_str + "\r\n");
--    request_body->append("Content-Disposition: form-data; name=\"" +
--                         WideToUTF8(pos->first) + "\"\r\n\r\n" +
--                         WideToUTF8(pos->second) + "\r\n");
-+  // Append the extra data as a single JSON form entry
-+  request_body->append("--" + boundary_str + "\r\n");
-+  request_body->append(
-+      "Content-Disposition: form-data; "
-+      "name=\"extra\"; "
-+      "filename=\"extra.json\"\r\n");
-+  request_body->append("Content-Type: application/json\r\n");
-+  request_body->append("\r\n");
-+
-+  if (!parameters.empty()) {
-+    request_body->append(parameters);
-   }
-+  request_body->append("\r\n");
- 
-   for (map<wstring, wstring>::const_iterator pos = files.begin();
-        pos != files.end(); ++pos) {
-     vector<char> contents;
-     if (!GetFileContents(pos->second, &contents)) {
-       return false;
-     }
- 
-@@ -394,27 +394,9 @@ string HTTPUpload::WideToMBCP(const wstr
-   WideCharToMultiByte(cp, 0, wide.c_str(), -1, buf, charcount,
-                       NULL, NULL);
- 
-   string result(buf);
-   delete[] buf;
-   return result;
- }
- 
--// static
--bool HTTPUpload::CheckParameters(const map<wstring, wstring> &parameters) {
--  for (map<wstring, wstring>::const_iterator pos = parameters.begin();
--       pos != parameters.end(); ++pos) {
--    const wstring &str = pos->first;
--    if (str.size() == 0) {
--      return false;  // disallow empty parameter names
--    }
--    for (unsigned int i = 0; i < str.size(); ++i) {
--      wchar_t c = str[i];
--      if (c < 32 || c == '"' || c > 127) {
--        return false;
--      }
--    }
--  }
--  return true;
--}
--
- }  // namespace google_breakpad
-diff --git a/src/common/windows/http_upload.h b/src/common/windows/http_upload.h
---- a/src/common/windows/http_upload.h
-+++ b/src/common/windows/http_upload.h
-@@ -24,17 +24,17 @@
- // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- 
- // HTTPUpload provides a "nice" API to send a multipart HTTP(S) POST
- // request using wininet.  It currently supports requests that contain
--// a set of string parameters (key/value pairs), and a file to upload.
-+// parameters encoded in a JSON string, and a file to upload.
- 
- #ifndef COMMON_WINDOWS_HTTP_UPLOAD_H_
- #define COMMON_WINDOWS_HTTP_UPLOAD_H_
- 
- #pragma warning(push)
- // Disable exception handler warnings.
- #pragma warning(disable : 4530)
- 
-@@ -42,36 +42,35 @@
- #include <wininet.h>
- 
- #include <map>
- #include <string>
- #include <vector>
- 
- namespace google_breakpad {
- 
-+using std::map;
- using std::string;
-+using std::vector;
- using std::wstring;
--using std::map;
--using std::vector;
- 
- class HTTPUpload {
-  public:
-   // Sends the given sets of parameters and files as a multipart POST
-   // request to the given URL.
-   // Each key in |files| is the name of the file part of the request
-   // (i.e. it corresponds to the name= attribute on an <input type="file">.
--  // Parameter names must contain only printable ASCII characters,
--  // and may not contain a quote (") character.
-+  // Parameters are specified as a JSON-encoded string in |parameters|.
-   // Only HTTP(S) URLs are currently supported.  Returns true on success.
-   // If the request is successful and response_body is non-NULL,
-   // the response body will be returned in response_body.
-   // If response_code is non-NULL, it will be set to the HTTP response code
-   // received (or 0 if the request failed before getting an HTTP response).
-   static bool SendRequest(const wstring &url,
--                          const map<wstring, wstring> &parameters,
-+                          const string &parameters,
-                           const map<wstring, wstring> &files,
-                           int *timeout,
-                           wstring *response_body,
-                           int *response_code);
- 
-  private:
-   class AutoInternetHandle;
- 
-@@ -82,20 +81,20 @@ class HTTPUpload {
-   static bool ReadResponse(HINTERNET request, wstring* response);
- 
-   // Generates a new multipart boundary for a POST request
-   static wstring GenerateMultipartBoundary();
- 
-   // Generates a HTTP request header for a multipart form submit.
-   static wstring GenerateRequestHeader(const wstring &boundary);
- 
--  // Given a set of parameters, a set of upload files, and a file part name,
-+  // Given a parameter string, a set of upload files, and a file part name,
-   // generates a multipart request body string with these parameters
-   // and minidump contents.  Returns true on success.
--  static bool GenerateRequestBody(const map<wstring, wstring> &parameters,
-+  static bool GenerateRequestBody(const string &parameters,
-                                   const map<wstring, wstring> &files,
-                                   const wstring &boundary,
-                                   string *request_body);
- 
-   // Fills the supplied vector with the contents of filename.
-   static bool GetFileContents(const wstring &filename, vector<char> *contents);
- 
-   // Converts a UTF8 string to UTF16.
-@@ -104,21 +103,16 @@ class HTTPUpload {
-   // Converts a UTF16 string to UTF8.
-   static string WideToUTF8(const wstring &wide) {
-       return WideToMBCP(wide, CP_UTF8);
-   }
- 
-   // Converts a UTF16 string to specified code page.
-   static string WideToMBCP(const wstring &wide, unsigned int cp);
- 
--  // Checks that the given list of parameters has only printable
--  // ASCII characters in the parameter name, and does not contain
--  // any quote (") characters.  Returns true if so.
--  static bool CheckParameters(const map<wstring, wstring> &parameters);
--
-   // No instances of this class should be created.
-   // Disallow all constructors, destructors, and operator=.
-   HTTPUpload();
-   explicit HTTPUpload(const HTTPUpload &);
-   void operator=(const HTTPUpload &);
-   ~HTTPUpload();
- };
- 
-
-changeset:   562937:9987d601c5eb
-parent:      562924:57f50ca45b4a
-user:        Gabriele Svelto <gsvelto@mozilla.com>
-date:        Thu Aug 01 15:19:52 2019 +0200
-summary:     Bug 1420363 - Linux-specific bits; r=froydnj
-
-diff --git a/src/common/linux/http_upload.cc b/src/common/linux/http_upload.cc
---- a/src/common/linux/http_upload.cc
-+++ b/src/common/linux/http_upload.cc
-@@ -50,30 +50,27 @@ static size_t WriteCallback(void *ptr, s
- }  // namespace
- 
- namespace google_breakpad {
- 
- static const char kUserAgent[] = "Breakpad/1.0 (Linux)";
- 
- // static
- bool HTTPUpload::SendRequest(const string &url,
--                             const map<string, string> &parameters,
-+                             const string &parameters,
-                              const map<string, string> &files,
-                              const string &proxy,
-                              const string &proxy_user_pwd,
-                              const string &ca_certificate_file,
-                              string *response_body,
-                              long *response_code,
-                              string *error_description) {
-   if (response_code != NULL)
-     *response_code = 0;
- 
--  if (!CheckParameters(parameters))
--    return false;
--
-   // We may have been linked statically; if curl_easy_init is in the
-   // current binary, no need to search for a dynamic version.
-   void* curl_lib = dlopen(NULL, RTLD_NOW);
-   if (!CheckCurlLib(curl_lib)) {
-     fprintf(stderr,
-             "Failed to open curl lib from binary, use libcurl.so instead\n");
-     dlerror();  // Clear dlerror before attempting to open libraries.
-     dlclose(curl_lib);
-@@ -128,24 +125,24 @@ bool HTTPUpload::SendRequest(const strin
-   if (!ca_certificate_file.empty())
-     (*curl_easy_setopt)(curl, CURLOPT_CAINFO, ca_certificate_file.c_str());
- 
-   struct curl_httppost *formpost = NULL;
-   struct curl_httppost *lastptr = NULL;
-   // Add form data.
-   CURLFORMcode (*curl_formadd)(struct curl_httppost **, struct curl_httppost **, ...);
-   *(void**) (&curl_formadd) = dlsym(curl_lib, "curl_formadd");
--  map<string, string>::const_iterator iter = parameters.begin();
--  for (; iter != parameters.end(); ++iter)
--    (*curl_formadd)(&formpost, &lastptr,
--                 CURLFORM_COPYNAME, iter->first.c_str(),
--                 CURLFORM_COPYCONTENTS, iter->second.c_str(),
--                 CURLFORM_END);
-+  (*curl_formadd)(&formpost, &lastptr, CURLFORM_COPYNAME, "extra",
-+                  CURLFORM_BUFFER, "extra.json", CURLFORM_BUFFERPTR,
-+                  parameters.c_str(), CURLFORM_BUFFERLENGTH,
-+                  parameters.length(), CURLFORM_CONTENTTYPE, "application/json",
-+                  CURLFORM_END);
- 
-   // Add form files.
-+  map<string, string>::const_iterator iter = files.begin();
-   for (iter = files.begin(); iter != files.end(); ++iter) {
-     (*curl_formadd)(&formpost, &lastptr,
-                  CURLFORM_COPYNAME, iter->first.c_str(),
-                  CURLFORM_FILE, iter->second.c_str(),
-                  CURLFORM_END);
-   }
- 
-   (*curl_easy_setopt)(curl, CURLOPT_HTTPPOST, formpost);
-@@ -205,26 +202,9 @@ bool HTTPUpload::SendRequest(const strin
- 
- // static
- bool HTTPUpload::CheckCurlLib(void* curl_lib) {
-   return curl_lib &&
-       dlsym(curl_lib, "curl_easy_init") &&
-       dlsym(curl_lib, "curl_easy_setopt");
- }
- 
--// static
--bool HTTPUpload::CheckParameters(const map<string, string> &parameters) {
--  for (map<string, string>::const_iterator pos = parameters.begin();
--       pos != parameters.end(); ++pos) {
--    const string &str = pos->first;
--    if (str.size() == 0)
--      return false;  // disallow empty parameter names
--    for (unsigned int i = 0; i < str.size(); ++i) {
--      int c = str[i];
--      if (c < 32 || c == '"' || c > 127) {
--        return false;
--      }
--    }
--  }
--  return true;
--}
--
- }  // namespace google_breakpad
-diff --git a/src/common/linux/http_upload.h b/src/common/linux/http_upload.h
---- a/src/common/linux/http_upload.h
-+++ b/src/common/linux/http_upload.h
-@@ -24,17 +24,17 @@
- // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- 
- // HTTPUpload provides a "nice" API to send a multipart HTTP(S) POST
- // request using libcurl.  It currently supports requests that contain
--// a set of string parameters (key/value pairs), and a file to upload.
-+// parameters encoded in a JSON string, and a file to upload.
- 
- #ifndef COMMON_LINUX_HTTP_UPLOAD_H__
- #define COMMON_LINUX_HTTP_UPLOAD_H__
- 
- #include <map>
- #include <string>
- 
- #include "common/using_std_string.h"
-@@ -44,41 +44,35 @@ namespace google_breakpad {
- using std::map;
- 
- class HTTPUpload {
-  public:
-   // Sends the given sets of parameters and files as a multipart POST
-   // request to the given URL.
-   // Each key in |files| is the name of the file part of the request
-   // (i.e. it corresponds to the name= attribute on an <input type="file">.
--  // Parameter names must contain only printable ASCII characters,
--  // and may not contain a quote (") character.
-+  // Parameters are specified as a JSON-encoded string in |parameters|.
-   // Only HTTP(S) URLs are currently supported.  Returns true on success.
-   // If the request is successful and response_body is non-NULL,
-   // the response body will be returned in response_body.
-   // If response_code is non-NULL, it will be set to the HTTP response code
-   // received (or 0 if the request failed before getting an HTTP response).
-   // If the send fails, a description of the error will be
-   // returned in error_description.
-   static bool SendRequest(const string &url,
--                          const map<string, string> &parameters,
-+                          const string &parameters,
-                           const map<string, string> &files,
-                           const string &proxy,
-                           const string &proxy_user_pwd,
-                           const string &ca_certificate_file,
-                           string *response_body,
-                           long *response_code,
-                           string *error_description);
- 
-  private:
--  // Checks that the given list of parameters has only printable
--  // ASCII characters in the parameter name, and does not contain
--  // any quote (") characters.  Returns true if so.
--  static bool CheckParameters(const map<string, string> &parameters);
--
-   // Checks the curl_lib parameter points to a valid curl lib.
-   static bool CheckCurlLib(void* curl_lib);
- 
-   // No instances of this class should be created.
-   // Disallow all constructors, destructors, and operator=.
-   HTTPUpload();
-   explicit HTTPUpload(const HTTPUpload &);
-   void operator=(const HTTPUpload &);
-
--- a/toolkit/crashreporter/client/crashreporter.cpp
+++ b/toolkit/crashreporter/client/crashreporter.cpp
@@ -19,17 +19,16 @@
 #include <cstring>
 #include <string>
 #include <utility>
 
 #ifdef XP_LINUX
 #  include <dlfcn.h>
 #endif
 
-#include "json/json.h"
 #include "nss.h"
 #include "sechash.h"
 
 using std::ifstream;
 using std::ios;
 using std::istream;
 using std::istringstream;
 using std::ofstream;
@@ -37,17 +36,16 @@ using std::ostream;
 using std::ostringstream;
 using std::string;
 using std::unique_ptr;
 using std::vector;
 
 namespace CrashReporter {
 
 StringTable gStrings;
-Json::Value gData;
 string gSettingsPath;
 string gEventsPath;
 string gPingPath;
 int gArgc;
 char** gArgv;
 bool gAutoSubmit;
 
 enum SubmissionResult { Succeeded, Failed };
@@ -124,28 +122,16 @@ bool ReadStringsFromFile(const string& p
     success = ReadStrings(*f, strings, unescape);
     f->close();
   }
 
   delete f;
   return success;
 }
 
-static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
-  bool success = false;
-  ifstream* f = UIOpenRead(aExtraDataPath, ios::in);
-  if (f->is_open()) {
-    Json::CharReaderBuilder builder;
-    success = parseFromStream(builder, *f, &aExtra, nullptr);
-  }
-
-  delete f;
-  return success;
-}
-
 static string Basename(const string& file) {
   string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR);
   if (slashIndex != string::npos)
     return file.substr(slashIndex + 1);
   else
     return file;
 }
 
@@ -153,89 +139,34 @@ static string GetDumpLocalID() {
   string localId = Basename(gReporterDumpFile);
   string::size_type dot = localId.rfind('.');
 
   if (dot == string::npos) return "";
 
   return localId.substr(0, dot);
 }
 
-static bool ReadEventFile(const string& aPath, string& aEventVersion,
-                          string& aTime, string& aUuid, Json::Value& aData) {
-  bool res = false;
-  ifstream* f = UIOpenRead(aPath, ios::binary);
-
-  if (f->is_open()) {
-    std::getline(*f, aEventVersion, '\n');
-    res = f->good();
-    std::getline(*f, aTime, '\n');
-    res &= f->good();
-    std::getline(*f, aUuid, '\n');
-    res &= f->good();
-
-    if (res) {
-      Json::CharReaderBuilder builder;
-      res = parseFromStream(builder, *f, &aData, nullptr);
-    }
-  }
-
-  delete f;
-  return res;
-}
-
-static void OverwriteEventFile(const string& aPath, const string& aEventVersion,
-                               const string& aTime, const string& aUuid,
-                               const Json::Value& aData) {
-  ofstream* f = UIOpenWrite(aPath, ios::binary | ios::trunc);
-  if (f->is_open()) {
-    f->write(aEventVersion.c_str(), aEventVersion.length()) << '\n';
-    f->write(aTime.c_str(), aTime.length()) << '\n';
-    f->write(aUuid.c_str(), aUuid.length()) << '\n';
-
-    Json::StreamWriterBuilder builder;
-    builder["indentation"] = "";
-    std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
-    writer->write(aData, f);
-    *f << "\n";
-  }
-
-  delete f;
-}
-
-static void UpdateEventFile(const Json::Value& aExtraData, const string& aHash,
-                            const string& aPingUuid) {
+// This appends the aKey/aValue entry to the main crash event so that it can
+// be picked up by Firefox once it restarts
+static void AppendToEventFile(const string& aKey, const string& aValue) {
   if (gEventsPath.empty()) {
     // If there is no path for finding the crash event, skip this step.
     return;
   }
 
   string localId = GetDumpLocalID();
   string path = gEventsPath + UI_DIR_SEPARATOR + localId;
-  string eventVersion;
-  string crashTime;
-  string crashUuid;
-  Json::Value eventData;
+  ofstream* f = UIOpenWrite(path, ios::app);
 
-  if (!ReadEventFile(path, eventVersion, crashTime, crashUuid, eventData)) {
-    return;
+  if (f->is_open()) {
+    *f << aKey << "=" << aValue << std::endl;
+    f->close();
   }
 
-  if (!aHash.empty()) {
-    eventData["MinidumpSha256Hash"] = aHash;
-  }
-
-  if (!aPingUuid.empty()) {
-    eventData["CrashPingUUID"] = aPingUuid;
-  }
-
-  if (aExtraData.isMember("StackTraces")) {
-    eventData["StackTraces"] = aExtraData["StackTraces"];
-  }
-
-  OverwriteEventFile(path, eventVersion, crashTime, crashUuid, eventData);
+  delete f;
 }
 
 static void WriteSubmissionEvent(SubmissionResult result,
                                  const string& remoteId) {
   if (gEventsPath.empty()) {
     // If there is no path for writing the submission event, skip it.
     return;
   }
@@ -532,23 +463,24 @@ static string ComputeDumpHash() {
     return "";  // If we encountered an error, return an empty hash
   }
 }
 
 }  // namespace CrashReporter
 
 using namespace CrashReporter;
 
-Json::Value kEmptyJsonString("");
-
-void RewriteStrings(Json::Value& aExtraData) {
+void RewriteStrings(StringTable& queryParameters) {
   // rewrite some UI strings with the values from the query parameters
-  string product = aExtraData.get("ProductName", kEmptyJsonString).asString();
-  Json::Value mozilla("Mozilla");
-  string vendor = aExtraData.get("Vendor", mozilla).asString();
+  string product = queryParameters["ProductName"];
+  string vendor = queryParameters["Vendor"];
+  if (vendor.empty()) {
+    // Assume Mozilla if no vendor is specified
+    vendor = "Mozilla";
+  }
 
   char buf[4096];
   UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(),
               vendor.c_str());
   gStrings[ST_CRASHREPORTERTITLE] = buf;
 
   string str = gStrings[ST_CRASHREPORTERPRODUCTERROR];
   // Only do the replacement here if the string has two
@@ -585,23 +517,18 @@ void RewriteStrings(Json::Value& aExtraD
   UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_QUIT].c_str(), product.c_str());
   gStrings[ST_QUIT] = buf;
 
   UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_ERROR_ENDOFLIFE].c_str(),
               product.c_str());
   gStrings[ST_ERROR_ENDOFLIFE] = buf;
 }
 
-bool CheckEndOfLifed(const Json::Value& aVersion) {
-  if (!aVersion.isString()) {
-    return false;
-  }
-
-  string reportPath =
-      gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + aVersion.asString();
+bool CheckEndOfLifed(string version) {
+  string reportPath = gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version;
   return UIFileExists(reportPath);
 }
 
 static string GetProgramPath(const string& exename) {
   string path = gArgv[0];
   size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
   path.erase(pos);
 #ifdef XP_MACOSX
@@ -668,83 +595,93 @@ int main(int argc, char** argv) {
     }
 
     gMemoryFile =
         GetAdditionalFilename(gReporterDumpFile, kMemoryReportExtension);
     if (!UIFileExists(gMemoryFile)) {
       gMemoryFile.erase();
     }
 
-    Json::Value extraData;
-    if (!ReadExtraFile(gExtraFile, extraData)) {
+    StringTable queryParameters;
+    if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) {
       UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
       return 0;
     }
 
-    if (!extraData.isMember("ProductName")) {
+    if (queryParameters.find("ProductName") == queryParameters.end()) {
       UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
       return 0;
     }
 
     // There is enough information in the extra file to rewrite strings
     // to be product specific
-    RewriteStrings(extraData);
+    RewriteStrings(queryParameters);
 
-    if (!extraData.isMember("ServerURL")) {
+    if (queryParameters.find("ServerURL") == queryParameters.end()) {
       UIError(gStrings[ST_ERROR_NOSERVERURL]);
       return 0;
     }
 
     // Hopefully the settings path exists in the environment. Try that before
     // asking the platform-specific code to guess.
     gSettingsPath = UIGetEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY");
     if (gSettingsPath.empty()) {
-      string product =
-          extraData.get("ProductName", kEmptyJsonString).asString();
-      string vendor = extraData.get("Vendor", kEmptyJsonString).asString();
-
+      string product = queryParameters["ProductName"];
+      string vendor = queryParameters["Vendor"];
       if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
         gSettingsPath.clear();
       }
     }
 
     if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
       UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
       return 0;
     }
 
     OpenLogFile();
 
     gEventsPath = UIGetEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY");
     gPingPath = UIGetEnv("MOZ_CRASHREPORTER_PING_DIRECTORY");
 
     // Assemble and send the crash ping
-    string hash = ComputeDumpHash();
-
+    string hash;
     string pingUuid;
-    SendCrashPing(extraData, hash, pingUuid, gPingPath);
-    UpdateEventFile(extraData, hash, pingUuid);
+
+    hash = ComputeDumpHash();
+    if (!hash.empty()) {
+      AppendToEventFile("MinidumpSha256Hash", hash);
+    }
+
+    if (SendCrashPing(queryParameters, hash, pingUuid, gPingPath)) {
+      AppendToEventFile("CrashPingUUID", pingUuid);
+    }
+
+    // Update the crash event with stacks if they are present
+    auto stackTracesItr = queryParameters.find("StackTraces");
+    if (stackTracesItr != queryParameters.end()) {
+      AppendToEventFile(stackTracesItr->first, stackTracesItr->second);
+    }
 
     if (!UIFileExists(gReporterDumpFile)) {
       UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
       return 0;
     }
 
     string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
     if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile,
                        gMemoryFile)) {
       return 0;
     }
 
-    string sendURL = extraData.get("ServerURL", kEmptyJsonString).asString();
+    string sendURL = queryParameters["ServerURL"];
     // we don't need to actually send these
-    extraData.removeMember("ServerURL");
-    extraData.removeMember("StackTraces");
+    queryParameters.erase("ServerURL");
+    queryParameters.erase("StackTraces");
 
-    extraData["Throttleable"] = "1";
+    queryParameters["Throttleable"] = "1";
 
     // re-set XUL_APP_FILE for xulrunner wrapped apps
     const char* appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE");
     if (appfile && *appfile) {
       const char prefix[] = "XUL_APP_FILE=";
       char* env = (char*)malloc(strlen(appfile) + strlen(prefix) + 1);
       if (!env) {
         UIError("Out of memory");
@@ -774,33 +711,31 @@ int main(int argc, char** argv) {
     // XXX: remove this in the far future when our robot
     // masters force everyone to use XULRunner
     char* urlEnv = getenv("MOZ_CRASHREPORTER_URL");
     if (urlEnv && *urlEnv) {
       sendURL = urlEnv;
     }
 
     // see if this version has been end-of-lifed
-
-    if (extraData.isMember("Version") &&
-        CheckEndOfLifed(extraData["Version"])) {
+    if (queryParameters.find("Version") != queryParameters.end() &&
+        CheckEndOfLifed(queryParameters["Version"])) {
       UIError(gStrings[ST_ERROR_ENDOFLIFE]);
       DeleteDump();
       return 0;
     }
 
     StringTable files;
     files["upload_file_minidump"] = gReporterDumpFile;
     if (!gMemoryFile.empty()) {
       files["memory_report"] = gMemoryFile;
     }
 
-    if (!UIShowCrashUI(files, extraData, sendURL, restartArgs)) {
+    if (!UIShowCrashUI(files, queryParameters, sendURL, restartArgs))
       DeleteDump();
-    }
   }
 
   UIShutdown();
 
   return 0;
 }
 
 #if defined(XP_WIN) && !defined(__GNUC__)
--- a/toolkit/crashreporter/client/crashreporter.h
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -32,18 +32,16 @@ std::string WideToUTF8(const std::wstrin
 
 #else
 
 #  define UI_SNPRINTF snprintf
 #  define UI_DIR_SEPARATOR "/"
 
 #endif
 
-#include "json/json.h"
-
 #define UI_CRASH_REPORTER_FILENAME "crashreporter"
 #define UI_MINIDUMP_ANALYZER_FILENAME "minidump-analyzer"
 #define UI_PING_SENDER_FILENAME "pingsender"
 
 typedef std::map<std::string, std::string> StringTable;
 
 #define ST_CRASHREPORTERTITLE "CrashReporterTitle"
 #define ST_CRASHREPORTERVENDORTITLE "CrashReporterVendorTitle"
@@ -100,17 +98,17 @@ void SendCompleted(bool success, const s
 
 bool ReadStrings(std::istream& in, StringTable& strings, bool unescape);
 bool ReadStringsFromFile(const std::string& path, StringTable& strings,
                          bool unescape);
 void LogMessage(const std::string& message);
 void DeleteDump();
 
 // Telemetry ping
-bool SendCrashPing(Json::Value& extra, const std::string& hash,
+bool SendCrashPing(StringTable& strings, const std::string& hash,
                    std::string& pingUuid, const std::string& pingDir);
 
 static const unsigned int kSaveCount = 10;
 }  // namespace CrashReporter
 
 //=============================================================================
 // implemented in the platform-specific files
 //=============================================================================
@@ -119,17 +117,17 @@ bool UIInit();
 void UIShutdown();
 
 // Run the UI for when the app was launched without a dump file
 void UIShowDefaultUI();
 
 // Run the UI for when the app was launched with a dump file
 // Return true if the user sent (or tried to send) the crash report,
 // false if they chose not to, and it should be deleted.
-bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
+bool UIShowCrashUI(const StringTable& files, const StringTable& queryParameters,
                    const std::string& sendURL,
                    const std::vector<std::string>& restartArgs);
 
 void UIError_impl(const std::string& message);
 
 bool UIGetIniPath(std::string& path);
 bool UIGetSettingsPath(const std::string& vendor, const std::string& product,
                        std::string& settingsPath);
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
@@ -42,17 +42,17 @@ GtkWidget* gIncludeURLCheck = 0;
 GtkWidget* gThrobber = 0;
 GtkWidget* gProgressLabel = 0;
 GtkWidget* gCloseButton = 0;
 GtkWidget* gRestartButton = 0;
 
 bool gInitialized = false;
 bool gDidTrySend = false;
 StringTable gFiles;
-Json::Value gQueryParameters;
+StringTable gQueryParameters;
 string gHttpProxy;
 string gAuth;
 string gCACertificateFile;
 string gSendURL;
 string gURLParameter;
 vector<string> gRestartArgs;
 GThread* gSendThreadID;
 
@@ -175,25 +175,21 @@ void LoadProxyinfo() {
   }
 
   g_object_unref(conf);
 
   // Don't dlclose gconfLib as libORBit-2 uses atexit().
 }
 
 gpointer SendThread(gpointer args) {
-  Json::StreamWriterBuilder builder;
-  builder["indentation"] = "";
-  string parameters(writeString(builder, gQueryParameters));
-
   string response, error;
   long response_code;
 
   bool success = google_breakpad::HTTPUpload::SendRequest(
-      gSendURL, parameters, gFiles, gHttpProxy, gAuth, gCACertificateFile,
+      gSendURL, gQueryParameters, gFiles, gHttpProxy, gAuth, gCACertificateFile,
       &response, &response_code, &error);
   if (success) {
     LogMessage("Crash report submitted successfully");
   } else {
     LogMessage("Crash report submission failed: " + error);
   }
 
   SendCompleted(success, response);
@@ -242,17 +238,17 @@ void RestartClicked(GtkButton* button, g
   RestartApplication();
   MaybeSubmitReport();
 }
 
 static void UpdateURL() {
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck))) {
     gQueryParameters["URL"] = gURLParameter;
   } else {
-    gQueryParameters.removeMember("URL");
+    gQueryParameters.erase("URL");
   }
 }
 
 void SubmitReportChecked(GtkButton* sender, gpointer userData) {
   UpdateSubmit();
 }
 
 void IncludeURLClicked(GtkButton* sender, gpointer userData) { UpdateURL(); }
--- a/toolkit/crashreporter/client/crashreporter_gtk_common.h
+++ b/toolkit/crashreporter/client/crashreporter_gtk_common.h
@@ -6,35 +6,33 @@
 #define CRASHREPORTER_GTK_COMMON_H__
 
 #include <glib.h>
 #include <gtk/gtk.h>
 
 #include <string>
 #include <vector>
 
-#include "json/json.h"
-
 const char kIniFile[] = "crashreporter.ini";
 
 extern GtkWidget* gWindow;
 extern GtkWidget* gSubmitReportCheck;
 extern GtkWidget* gIncludeURLCheck;
 extern GtkWidget* gThrobber;
 extern GtkWidget* gProgressLabel;
 extern GtkWidget* gCloseButton;
 extern GtkWidget* gRestartButton;
 
 extern std::vector<std::string> gRestartArgs;
 extern GThread* gSendThreadID;
 
 extern bool gInitialized;
 extern bool gDidTrySend;
 extern StringTable gFiles;
-extern Json::Value gQueryParameters;
+extern StringTable gQueryParameters;
 extern std::string gHttpProxy;
 extern std::string gAuth;
 extern std::string gCACertificateFile;
 extern std::string gSendURL;
 extern std::string gURLParameter;
 
 void LoadProxyinfo();
 gpointer SendThread(gpointer args);
--- a/toolkit/crashreporter/client/crashreporter_linux.cpp
+++ b/toolkit/crashreporter/client/crashreporter_linux.cpp
@@ -170,30 +170,21 @@ static void ShowReportInfo(GtkTextView* 
   GtkTextBuffer* buffer = gtk_text_view_get_buffer(viewReportTextView);
 
   GtkTextIter start, end;
   gtk_text_buffer_get_start_iter(buffer, &start);
   gtk_text_buffer_get_end_iter(buffer, &end);
 
   gtk_text_buffer_delete(buffer, &start, &end);
 
-  for (Json::ValueConstIterator iter = gQueryParameters.begin();
-       iter != gQueryParameters.end(); ++iter) {
-    gtk_text_buffer_insert(buffer, &end, iter.name().c_str(),
-                           iter.name().length());
+  for (StringTable::iterator iter = gQueryParameters.begin();
+       iter != gQueryParameters.end(); iter++) {
+    gtk_text_buffer_insert(buffer, &end, iter->first.c_str(), -1);
     gtk_text_buffer_insert(buffer, &end, ": ", -1);
-    string value;
-    if (iter->isString()) {
-      value = iter->asString();
-    } else {
-      Json::StreamWriterBuilder builder;
-      builder["indentation"] = "";
-      value = writeString(builder, *iter);
-    }
-    gtk_text_buffer_insert(buffer, &end, value.c_str(), value.length());
+    gtk_text_buffer_insert(buffer, &end, iter->second.c_str(), -1);
     gtk_text_buffer_insert(buffer, &end, "\n", -1);
   }
 
   gtk_text_buffer_insert(buffer, &end, "\n", -1);
   gtk_text_buffer_insert(buffer, &end, gStrings[ST_EXTRAREPORTINFO].c_str(),
                          -1);
 }
 
@@ -247,21 +238,20 @@ static void ViewReportClicked(GtkButton*
   gtk_widget_destroy(GTK_WIDGET(dialog));
 }
 
 static void CommentChanged(GtkTextBuffer* buffer, gpointer userData) {
   GtkTextIter start, end;
   gtk_text_buffer_get_start_iter(buffer, &start);
   gtk_text_buffer_get_end_iter(buffer, &end);
   const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
-  if (comment[0] == '\0' || gCommentFieldHint) {
-    gQueryParameters.removeMember("Comments");
-  } else {
+  if (comment[0] == '\0' || gCommentFieldHint)
+    gQueryParameters.erase("Comments");
+  else
     gQueryParameters["Comments"] = comment;
-  }
 }
 
 static void CommentInsert(GtkTextBuffer* buffer, GtkTextIter* location,
                           gchar* text, gint len, gpointer userData) {
   GtkTextIter start, end;
   gtk_text_buffer_get_start_iter(buffer, &start);
   gtk_text_buffer_get_end_iter(buffer, &end);
   const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
@@ -327,21 +317,20 @@ static gboolean CommentFocusChange(GtkWi
 static void UpdateEmail() {
   const char* email = gtk_entry_get_text(GTK_ENTRY(gEmailEntry));
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gEmailMeCheck))) {
     gtk_widget_set_sensitive(gEmailEntry, TRUE);
   } else {
     email = "";
     gtk_widget_set_sensitive(gEmailEntry, FALSE);
   }
-  if (email[0] == '\0' || gEmailFieldHint) {
-    gQueryParameters.removeMember("Email");
-  } else {
+  if (email[0] == '\0' || gEmailFieldHint)
+    gQueryParameters.erase("Email");
+  else
     gQueryParameters["Email"] = email;
-  }
 }
 
 static void EmailMeClicked(GtkButton* sender, gpointer userData) {
   UpdateEmail();
 }
 
 static void EmailChanged(GtkEditable* editable, gpointer userData) {
   UpdateEmail();
@@ -398,25 +387,24 @@ void TryInitGnome() {
  *  UIOpenWrite
  */
 
 void UIShutdown() {
   if (gnomeuiLib) dlclose(gnomeuiLib);
   // Don't dlclose gnomeLib as libgnomevfs and libORBit-2 use atexit().
 }
 
-bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
+bool UIShowCrashUI(const StringTable& files, const StringTable& queryParameters,
                    const string& sendURL, const vector<string>& restartArgs) {
   gFiles = files;
   gQueryParameters = queryParameters;
   gSendURL = sendURL;
   gRestartArgs = restartArgs;
-  if (gQueryParameters.isMember("URL")) {
-    gURLParameter = gQueryParameters["URL"].asString();
-  }
+  if (gQueryParameters.find("URL") != gQueryParameters.end())
+    gURLParameter = gQueryParameters["URL"];
 
   if (gAutoSubmit) {
     SendReport();
     CloseApp(nullptr);
     return true;
   }
 
   gWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
@@ -508,17 +496,17 @@ bool UIShowCrashUI(const StringTable& fi
       gtk_text_view_get_buffer(GTK_TEXT_VIEW(gCommentText));
   g_signal_connect(commentBuffer, "changed", G_CALLBACK(CommentChanged), 0);
   g_signal_connect(commentBuffer, "insert-text", G_CALLBACK(CommentInsert), 0);
 
   gtk_container_add(GTK_CONTAINER(scrolled), gCommentText);
   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gCommentText), GTK_WRAP_WORD_CHAR);
   gtk_widget_set_size_request(GTK_WIDGET(gCommentText), -1, 100);
 
-  if (gQueryParameters.isMember("URL")) {
+  if (gQueryParameters.find("URL") != gQueryParameters.end()) {
     gIncludeURLCheck =
         gtk_check_button_new_with_label(gStrings[ST_CHECKURL].c_str());
     gtk_box_pack_start(GTK_BOX(innerVBox), gIncludeURLCheck, FALSE, FALSE, 0);
     g_signal_connect(gIncludeURLCheck, "clicked", G_CALLBACK(IncludeURLClicked),
                      0);
     // on by default
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck), TRUE);
   }
--- a/toolkit/crashreporter/client/crashreporter_osx.h
+++ b/toolkit/crashreporter/client/crashreporter_osx.h
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CRASHREPORTER_OSX_H__
 #define CRASHREPORTER_OSX_H__
 
 #include <Cocoa/Cocoa.h>
 #include "HTTPMultipartUpload.h"
 #include "crashreporter.h"
-#include "json/json.h"
 
 // Defined below
 @class TextViewWithPlaceHolder;
 
 @interface CrashReporterUI : NSObject {
   IBOutlet NSWindow* mWindow;
 
   /* Crash reporter view */
@@ -42,17 +41,17 @@
   IBOutlet NSWindow* mViewReportWindow;
   IBOutlet NSTextView* mViewReportTextView;
   IBOutlet NSButton* mViewReportOkButton;
 
   HTTPMultipartUpload* mPost;
 }
 
 - (void)showCrashUI:(const StringTable&)files
-    queryParameters:(const Json::Value&)queryParameters
+    queryParameters:(const StringTable&)queryParameters
             sendURL:(const std::string&)sendURL;
 - (void)showErrorUI:(const std::string&)message;
 - (void)showReportInfo;
 - (void)maybeSubmitReport;
 - (void)closeMeDown:(id)unused;
 
 - (IBAction)submitReportClicked:(id)sender;
 - (IBAction)viewReportClicked:(id)sender;
--- a/toolkit/crashreporter/client/crashreporter_osx.mm
+++ b/toolkit/crashreporter/client/crashreporter_osx.mm
@@ -18,17 +18,17 @@ using std::string;
 using std::vector;
 using std::ostringstream;
 
 using namespace CrashReporter;
 
 static NSAutoreleasePool* gMainPool;
 static CrashReporterUI* gUI = 0;
 static StringTable gFiles;
-static Json::Value gQueryParameters;
+static StringTable gQueryParameters;
 static string gURLParameter;
 static string gSendURL;
 static vector<string> gRestartArgs;
 static bool gDidTrySend = false;
 static bool gRTLlayout = false;
 
 static cpu_type_t pref_cpu_types[2] = {
 #if defined(__i386__)
@@ -87,17 +87,17 @@ static bool RestartApplication() {
   gUI = self;
   [mWindow center];
 
   [mWindow setTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]];
   [NSApp activateIgnoringOtherApps:YES];
 }
 
 - (void)showCrashUI:(const StringTable&)files
-    queryParameters:(const Json::Value&)queryParameters
+    queryParameters:(const StringTable&)queryParameters
             sendURL:(const string&)sendURL {
   gFiles = files;
   gQueryParameters = queryParameters;
   gSendURL = sendURL;
 
   if (gAutoSubmit) {
     gDidTrySend = true;
     [self sendReport];
@@ -122,19 +122,19 @@ static bool RestartApplication() {
   [mIncludeURLButton setTitle:Str(ST_CHECKURL)];
   [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)];
   [mViewReportOkButton setTitle:Str(ST_OK)];
 
   [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)];
   if (gRTLlayout) [mCommentText toggleBaseWritingDirection:self];
   [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)];
 
-  if (gQueryParameters.isMember("URL")) {
+  if (gQueryParameters.find("URL") != gQueryParameters.end()) {
     // save the URL value in case the checkbox gets unchecked
-    gURLParameter = gQueryParameters["URL"].asString();
+    gURLParameter = gQueryParameters["URL"];
   } else {
     // no URL specified, hide checkbox
     [mIncludeURLButton removeFromSuperview];
     // shrink window to fit
     NSRect frame = [mWindow frame];
     NSRect includeURLFrame = [mIncludeURLButton frame];
     NSRect emailFrame = [mEmailMeButton frame];
     int buttonMask = [mViewReportButton autoresizingMask];
@@ -192,30 +192,23 @@ static bool RestartApplication() {
   NSDictionary* boldAttr =
       [NSDictionary dictionaryWithObject:[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]]
                                   forKey:NSFontAttributeName];
   NSDictionary* normalAttr =
       [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]
                                   forKey:NSFontAttributeName];
 
   [mViewReportTextView setString:@""];
-  for (Json::ValueConstIterator iter = gQueryParameters.begin(); iter != gQueryParameters.end();
-       ++iter) {
-    NSAttributedString* key = [[NSAttributedString alloc] initWithString:NSSTR(iter.name() + ": ")
+  for (StringTable::iterator iter = gQueryParameters.begin(); iter != gQueryParameters.end();
+       iter++) {
+    NSAttributedString* key = [[NSAttributedString alloc] initWithString:NSSTR(iter->first + ": ")
                                                               attributes:boldAttr];
-    string str;
-    if (iter->isString()) {
-      str = iter->asString();
-    } else {
-      Json::StreamWriterBuilder builder;
-      builder["indentation"] = "";
-      str = writeString(builder, *iter);
-    }
-    NSAttributedString* value = [[NSAttributedString alloc] initWithString:NSSTR(str + "\n")
-                                                                attributes:normalAttr];
+    NSAttributedString* value =
+        [[NSAttributedString alloc] initWithString:NSSTR(iter->second + "\n")
+                                        attributes:normalAttr];
     [[mViewReportTextView textStorage] appendAttributedString:key];
     [[mViewReportTextView textStorage] appendAttributedString:value];
     [key release];
     [value release];
   }
 
   NSAttributedString* extra =
       [[NSAttributedString alloc] initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO])
@@ -286,17 +279,17 @@ static bool RestartApplication() {
   [self updateEmail];
 }
 
 - (void)textDidChange:(NSNotification*)aNotification {
   // update comment parameter
   if ([[[mCommentText textStorage] mutableString] length] > 0)
     gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString] UTF8String];
   else
-    gQueryParameters.removeMember("Comments");
+    gQueryParameters.erase("Comments");
 }
 
 // Limit the comment field to 500 bytes in UTF-8
 - (BOOL)textView:(NSTextView*)aTextView
     shouldChangeTextInRange:(NSRange)affectedCharRange
           replacementString:(NSString*)replacementString {
   // current string length + replacement text length - replaced range length
   if (([[aTextView string] lengthOfBytesUsingEncoding:NSUTF8StringEncoding] +
@@ -461,27 +454,27 @@ static bool RestartApplication() {
     [self enableControls:NO];
   }
 }
 
 - (void)updateURL {
   if ([mIncludeURLButton state] == NSOnState && !gURLParameter.empty()) {
     gQueryParameters["URL"] = gURLParameter;
   } else {
-    gQueryParameters.removeMember("URL");
+    gQueryParameters.erase("URL");
   }
 }
 
 - (void)updateEmail {
   if ([mEmailMeButton state] == NSOnState && [mSubmitReportButton state] == NSOnState) {
     NSString* email = [mEmailText stringValue];
     gQueryParameters["Email"] = [email UTF8String];
     [mEmailText setEnabled:YES];
   } else {
-    gQueryParameters.removeMember("Email");
+    gQueryParameters.erase("Email");
     [mEmailText setEnabled:NO];
   }
 }
 
 - (void)sendReport {
   if (![self setupPost]) {
     LogMessage("Crash report submission failed: could not set up POST data");
 
@@ -501,25 +494,38 @@ static bool RestartApplication() {
   NSURL* url =
       [NSURL URLWithString:[NSSTR(gSendURL)
                                stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
   if (!url) return false;
 
   mPost = [[HTTPMultipartUpload alloc] initWithURL:url];
   if (!mPost) return false;
 
+  NSMutableDictionary* parameters =
+      [[NSMutableDictionary alloc] initWithCapacity:gQueryParameters.size()];
+  if (!parameters) return false;
+
+  StringTable::const_iterator end = gQueryParameters.end();
+  for (StringTable::const_iterator i = gQueryParameters.begin(); i != end; i++) {
+    NSString* key = NSSTR(i->first);
+    NSString* value = NSSTR(i->second);
+    if (key && value) {
+      [parameters setObject:value forKey:key];
+    } else {
+      ostringstream message;
+      message << "Warning: skipping annotation '" << i->first
+              << "' due to malformed UTF-8 encoding";
+      LogMessage(message.str());
+    }
+  }
+
   for (StringTable::const_iterator i = gFiles.begin(); i != gFiles.end(); i++) {
     [mPost addFileAtPath:NSSTR(i->second) name:NSSTR(i->first)];
   }
 
-  Json::StreamWriterBuilder builder;
-  builder["indentation"] = "";
-  string output = writeString(builder, gQueryParameters).append("\r\n");
-  NSMutableString* parameters = [[NSMutableString alloc] initWithUTF8String:output.c_str()];
-
   [mPost setParameters:parameters];
   [parameters release];
 
   return true;
 }
 
 - (void)uploadComplete:(NSData*)data {
   NSHTTPURLResponse* response = [mPost response];
@@ -683,17 +689,17 @@ bool UIInit() {
 
 void UIShutdown() { [gMainPool release]; }
 
 void UIShowDefaultUI() {
   [gUI showErrorUI:gStrings[ST_CRASHREPORTERDEFAULT]];
   [NSApp run];
 }
 
-bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
+bool UIShowCrashUI(const StringTable& files, const StringTable& queryParameters,
                    const string& sendURL, const vector<string>& restartArgs) {
   gRestartArgs = restartArgs;
 
   [gUI showCrashUI:files queryParameters:queryParameters sendURL:sendURL];
   [NSApp run];
 
   return gDidTrySend;
 }
--- a/toolkit/crashreporter/client/crashreporter_win.cpp
+++ b/toolkit/crashreporter/client/crashreporter_win.cpp
@@ -49,17 +49,17 @@ using std::set;
 using std::string;
 using std::vector;
 using std::wstring;
 
 using namespace CrashReporter;
 
 typedef struct {
   HWND hDlg;
-  Json::Value queryParameters;
+  map<wstring, wstring> queryParameters;
   map<wstring, wstring> files;
   wstring sendURL;
 
   wstring serverResponse;
 } SendThreadData;
 
 /*
  * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx
@@ -77,19 +77,19 @@ typedef struct {
   // instance, so I've omitted the rest.
 } DLGTEMPLATEEX;
 
 static HANDLE gThreadHandle;
 static SendThreadData gSendData = {
     0,
 };
 static vector<string> gRestartArgs;
-static Json::Value gQueryParameters;
+static map<wstring, wstring> gQueryParameters;
 static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
-static string gURLParameter;
+static wstring gURLParameter;
 static int gCheckboxPadding = 6;
 static bool gRTLlayout = false;
 
 // When vertically resizing the dialog, these items should move down
 static set<UINT> gAttachedBottom;
 
 // Default set of items for gAttachedBottom
 static const UINT kDefaultAttachedBottom[] = {
@@ -346,22 +346,19 @@ static void ReflowDialog(HWND hwndDlg, i
 static DWORD WINAPI SendThreadProc(LPVOID param) {
   bool finishedOk;
   SendThreadData* td = (SendThreadData*)param;
 
   if (td->sendURL.empty()) {
     finishedOk = false;
     LogMessage("No server URL, not sending report");
   } else {
-    Json::StreamWriterBuilder builder;
-    builder["indentation"] = "";
-    string parameters(Json::writeString(builder, td->queryParameters));
     google_breakpad::CrashReportSender sender(L"");
-    finishedOk = (sender.SendCrashReport(td->sendURL, parameters, td->files,
-                                         &td->serverResponse) ==
+    finishedOk = (sender.SendCrashReport(td->sendURL, td->queryParameters,
+                                         td->files, &td->serverResponse) ==
                   google_breakpad::RESULT_SUCCEEDED);
     if (finishedOk) {
       LogMessage("Crash report submitted successfully");
     } else {
       // get an error string and print it to the log
       // XXX: would be nice to get the HTTP status code here, filed:
       // http://code.google.com/p/google-breakpad/issues/detail?id=220
       LogMessage(FormatLastError());
@@ -485,68 +482,60 @@ static void RestartApplication() {
     CloseHandle(pi.hProcess);
     CloseHandle(pi.hThread);
   }
 }
 
 static void ShowReportInfo(HWND hwndDlg) {
   wstring description;
 
-  for (Json::ValueConstIterator iter = gQueryParameters.begin();
-       iter != gQueryParameters.end(); ++iter) {
-    description += UTF8ToWide(iter.name());
+  for (map<wstring, wstring>::const_iterator i = gQueryParameters.begin();
+       i != gQueryParameters.end(); i++) {
+    description += i->first;
     description += L": ";
-    string value;
-    if (iter->isString()) {
-      value = iter->asString();
-    } else {
-      Json::StreamWriterBuilder builder;
-      builder["indentation"] = "";
-      value = Json::writeString(builder, *iter);
-    }
-    description += UTF8ToWide(value);
+    description += i->second;
     description += L"\n";
   }
 
   description += L"\n";
   description += Str(ST_EXTRAREPORTINFO);
 
   SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str());
 }
 
 static void UpdateURL(HWND hwndDlg) {
   if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) {
-    gQueryParameters["URL"] = gURLParameter;
+    gQueryParameters[L"URL"] = gURLParameter;
   } else {
-    gQueryParameters.removeMember("URL");
+    gQueryParameters.erase(L"URL");
   }
 }
 
 static void UpdateEmail(HWND hwndDlg) {
   if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) {
     wchar_t email[MAX_EMAIL_LENGTH];
     GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email,
                     sizeof(email) / sizeof(email[0]));
-    gQueryParameters["Email"] = WideToUTF8(email);
+    gQueryParameters[L"Email"] = email;
     if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK))
       EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true);
   } else {
-    gQueryParameters.removeMember("Email");
+    gQueryParameters.erase(L"Email");
     EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false);
   }
 }
 
 static void UpdateComment(HWND hwndDlg) {
   wchar_t comment[MAX_COMMENT_LENGTH + 1];
   GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment,
                   sizeof(comment) / sizeof(comment[0]));
   if (wcslen(comment) > 0)
-    gQueryParameters["Comments"] = WideToUTF8(comment);
+    gQueryParameters[L"Comments"] = comment;
   else
-    gQueryParameters.removeMember("Comments");
+    gQueryParameters.erase(L"Comments");
 }
 
 /*
  * Dialog procedure for the "view report" dialog.
  */
 static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message,
                                           WPARAM wParam, LPARAM lParam) {
   switch (message) {
@@ -976,17 +965,17 @@ static BOOL CALLBACK CrashReporterDialog
       SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0);
       // Force redraw.
       SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETTARGETDEVICE,
                          (WPARAM) nullptr, 0);
       // Force resize.
       SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_REQUESTRESIZE, 0, 0);
 
       // if no URL was given, hide the URL checkbox
-      if (!gQueryParameters.isMember("URL")) {
+      if (gQueryParameters.find(L"URL") == gQueryParameters.end()) {
         RECT urlCheckRect, emailCheckRect;
         GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect);
         GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect);
 
         SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false);
 
         gAttachedBottom.erase(IDC_VIEWREPORTBUTTON);
         gAttachedBottom.erase(IDC_SUBMITREPORTCHECK);
@@ -1206,49 +1195,49 @@ static bool CanUseMainCrashReportServer(
   }
 
   // Otherwise we have an NT 5 client.
   // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2).
   return (IsWindowsVersionOrGreater(5, 1, 3) &&
           !IsWindowsVersionOrGreater(5, 2, 0));
 }
 
-bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
+bool UIShowCrashUI(const StringTable& files, const StringTable& queryParameters,
                    const string& sendURL, const vector<string>& restartArgs) {
   gSendData.hDlg = nullptr;
   gSendData.sendURL = UTF8ToWide(sendURL);
 
   // Older Windows don't support the crash report server's crypto.
   // This is a hack to use an alternate server.
   if (!CanUseMainCrashReportServer() &&
       gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) {
     gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1,
                               SENDURL_XPSP2);
   }
 
   for (StringTable::const_iterator i = files.begin(); i != files.end(); i++) {
     gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
   }
 
-  gQueryParameters = queryParameters;
-
-  if (gQueryParameters.isMember("Vendor")) {
-    gCrashReporterKey = L"Software\\";
-    string vendor = gQueryParameters["Vendor"].asString();
-    if (!vendor.empty()) {
-      gCrashReporterKey += UTF8ToWide(vendor) + L"\\";
-    }
-    string productName = gQueryParameters["ProductName"].asString();
-    gCrashReporterKey += UTF8ToWide(productName) + L"\\Crash Reporter";
+  for (StringTable::const_iterator i = queryParameters.begin();
+       i != queryParameters.end(); i++) {
+    gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second);
   }
 
-  if (gQueryParameters.isMember("URL")) {
-    gURLParameter = gQueryParameters["URL"].asString();
+  if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) {
+    gCrashReporterKey = L"Software\\";
+    if (!gQueryParameters[L"Vendor"].empty()) {
+      gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\";
+    }
+    gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter";
   }
 
+  if (gQueryParameters.find(L"URL") != gQueryParameters.end())
+    gURLParameter = gQueryParameters[L"URL"];
+
   gRestartArgs = restartArgs;
 
   if (gStrings.find("isRTL") != gStrings.end() && gStrings["isRTL"] == "yes")
     gRTLlayout = true;
 
   if (gAutoSubmit) {
     gSendData.queryParameters = gQueryParameters;
 
--- a/toolkit/crashreporter/client/ping.cpp
+++ b/toolkit/crashreporter/client/ping.cpp
@@ -110,53 +110,57 @@ static string CurrentDate(string format)
 
 const char kTelemetryClientId[] = "TelemetryClientId";
 const char kTelemetryUrl[] = "TelemetryServerURL";
 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(const Json::Value& aExtra) {
+static Json::Value CreateMetadataNode(StringTable& strings) {
   Json::Value node;
 
-  for (Json::ValueConstIterator iter = aExtra.begin(); iter != aExtra.end();
-       ++iter) {
+  for (auto line : strings) {
     Annotation annotation;
 
-    if (AnnotationFromString(annotation, iter.memberName())) {
+    if (AnnotationFromString(annotation, line.first.c_str())) {
       if (IsAnnotationWhitelistedForPing(annotation)) {
-        node[iter.memberName()] = *iter;
+        node[line.first] = line.second;
       }
     }
   }
 
   return node;
 }
 
 // Create the payload node of the crash ping
-static Json::Value CreatePayloadNode(const Json::Value& aExtra,
-                                     const string& aHash,
+static Json::Value CreatePayloadNode(StringTable& strings, const string& aHash,
                                      const string& aSessionId) {
   Json::Value payload;
 
   payload["sessionId"] = aSessionId;
   payload["version"] = 1;
   payload["crashDate"] = CurrentDate(kISO8601Date);
   payload["crashTime"] = CurrentDate(kISO8601DateHours);
   payload["hasCrashEnvironment"] = true;
   payload["crashId"] = GetDumpLocalID();
   payload["minidumpSha256Hash"] = aHash;
   payload["processType"] = "main";  // This is always a main crash
-  if (aExtra.isMember("StackTraces")) {
-    payload["stackTraces"] = aExtra["StackTraces"];
+
+  // Parse the stack traces
+  Json::Value stackTracesValue;
+  Json::Reader reader;
+
+  if (reader.parse(strings["StackTraces"], stackTracesValue,
+                   /* collectComments */ false)) {
+    payload["stackTraces"] = stackTracesValue;
   }
 
   // Assemble the payload metadata
-  payload["metadata"] = CreateMetadataNode(aExtra);
+  payload["metadata"] = CreateMetadataNode(strings);
 
   return payload;
 }
 
 // Create the application node of the crash ping
 static Json::Value CreateApplicationNode(
     const string& aVendor, const string& aName, const string& aVersion,
     const string& aDisplayVersion, const string& aPlatformVersion,
@@ -177,36 +181,38 @@ static Json::Value CreateApplicationNode
   if (!aXpcomAbi.empty()) {
     application["xpcomAbi"] = aXpcomAbi;
   }
 
   return application;
 }
 
 // Create the root node of the crash ping
-static Json::Value CreateRootNode(
-    const Json::Value& aExtra, const string& aUuid, const string& aHash,
-    const string& aClientId, const string& aSessionId, const string& aName,
-    const string& aVersion, const string& aChannel, const string& aBuildId) {
+static Json::Value CreateRootNode(StringTable& strings, const string& aUuid,
+                                  const string& aHash, const string& aClientId,
+                                  const string& aSessionId, const string& aName,
+                                  const string& aVersion,
+                                  const string& aChannel,
+                                  const string& aBuildId) {
   Json::Value root;
   root["type"] = "crash";  // This is a crash ping
   root["id"] = aUuid;
   root["version"] = kTelemetryVersion;
   root["creationDate"] = CurrentDate(kISO8601DateHours);
   root["clientId"] = aClientId;
 
   // Parse the telemetry environment
   Json::Value environment;
   Json::Reader reader;
   string architecture;
   string xpcomAbi;
   string displayVersion;
   string platformVersion;
 
-  if (reader.parse(aExtra["TelemetryEnvironment"].asString(), environment,
+  if (reader.parse(strings["TelemetryEnvironment"], environment,
                    /* collectComments */ false)) {
     if (environment.isMember("build") && environment["build"].isObject()) {
       Json::Value build = environment["build"];
       if (build.isMember("architecture") && build["architecture"].isString()) {
         architecture = build["architecture"].asString();
       }
       if (build.isMember("xpcomAbi") && build["xpcomAbi"].isString()) {
         xpcomAbi = build["xpcomAbi"].asString();
@@ -219,20 +225,20 @@ static Json::Value CreateRootNode(
           build["platformVersion"].isString()) {
         platformVersion = build["platformVersion"].asString();
       }
     }
 
     root["environment"] = environment;
   }
 
-  root["payload"] = CreatePayloadNode(aExtra, aHash, aSessionId);
+  root["payload"] = CreatePayloadNode(strings, aHash, aSessionId);
   root["application"] = CreateApplicationNode(
-      aExtra["Vendor"].asString(), aName, aVersion, displayVersion,
-      platformVersion, aChannel, aBuildId, architecture, xpcomAbi);
+      strings["Vendor"], aName, aVersion, displayVersion, platformVersion,
+      aChannel, aBuildId, architecture, xpcomAbi);
 
   return root;
 }
 
 // Generates the URL used to submit the crash ping, see TelemetrySend.jsm
 string GenerateSubmissionUrl(const string& aUrl, const string& aId,
                              const string& aName, const string& aVersion,
                              const string& aChannel, const string& aBuildId) {
@@ -253,73 +259,64 @@ static bool WritePing(const string& aPat
     f->close();
     success = f->good();
   }
 
   delete f;
   return success;
 }
 
-// Assembles the crash ping using the JSON data extracted from the .extra file
+// Assembles the crash ping using the strings extracted from the .extra file
 // and sends it using the crash sender. All the telemetry specific data but the
 // environment will be stripped from the annotations so that it won't be sent
 // together with the crash report.
 //
 // Note that the crash ping sender is invoked in a fire-and-forget way so this
 // won't block waiting for the ping to be delivered.
 //
 // Returns true if the ping was assembled and handed over to the pingsender
-// correctly, also populates the aPingUuid parameter with the ping UUID. Returns
-// false otherwise and leaves the aPingUuid parameter unmodified.
-bool SendCrashPing(Json::Value& aExtra, const string& aHash, string& aPingUuid,
+// correctly, false otherwise and populates the aUUID field with the ping UUID.
+bool SendCrashPing(StringTable& strings, const string& aHash, string& pingUuid,
                    const string& pingDir) {
-  // Remove the telemetry-related data from the crash annotations
-  Json::Value value;
-  if (!aExtra.removeMember(kTelemetryClientId, &value)) {
-    return false;
-  }
-  string clientId = value.asString();
+  string clientId = strings[kTelemetryClientId];
+  string serverUrl = strings[kTelemetryUrl];
+  string sessionId = strings[kTelemetrySessionId];
 
-  if (!aExtra.removeMember(kTelemetryUrl, &value)) {
-    return false;
-  }
-  string serverUrl = value.asString();
+  // Remove the telemetry-related data from the crash annotations
+  strings.erase(kTelemetryClientId);
+  strings.erase(kTelemetryUrl);
+  strings.erase(kTelemetrySessionId);
 
-  if (!aExtra.removeMember(kTelemetrySessionId, &value)) {
-    return false;
-  }
-  string sessionId = value.asString();
-
-  string buildId = aExtra["BuildID"].asString();
-  string channel = aExtra["ReleaseChannel"].asString();
-  string name = aExtra["ProductName"].asString();
-  string version = aExtra["Version"].asString();
+  string buildId = strings["BuildID"];
+  string channel = strings["ReleaseChannel"];
+  string name = strings["ProductName"];
+  string version = strings["Version"];
   string uuid = GenerateUUID();
   string url =
       GenerateSubmissionUrl(serverUrl, uuid, name, version, channel, buildId);
 
   if (serverUrl.empty() || uuid.empty()) {
     return false;
   }
 
-  Json::Value root = CreateRootNode(aExtra, uuid, aHash, clientId, sessionId,
+  Json::Value root = CreateRootNode(strings, uuid, aHash, clientId, sessionId,
                                     name, version, channel, buildId);
 
   // Write out the result to the pending pings directory
   Json::FastWriter writer;
   string ping = writer.write(root);
   string pingPath = pingDir + UI_DIR_SEPARATOR + uuid + ".json";
 
   if (!WritePing(pingPath, ping)) {
     return false;
   }
 
   // Hand over the ping to the sender
   vector<string> args = {url, pingPath};
   if (UIRunProgram(GetProgramPath(UI_PING_SENDER_FILENAME), args)) {
-    aPingUuid = uuid;
+    pingUuid = uuid;
     return true;
   } else {
     return false;
   }
 }
 
 }  // namespace CrashReporter
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.cc
@@ -50,27 +50,30 @@ static size_t WriteCallback(void *ptr, s
 }  // namespace
 
 namespace google_breakpad {
 
 static const char kUserAgent[] = "Breakpad/1.0 (Linux)";
 
 // static
 bool HTTPUpload::SendRequest(const string &url,
-                             const string &parameters,
+                             const map<string, string> &parameters,
                              const map<string, string> &files,
                              const string &proxy,
                              const string &proxy_user_pwd,
                              const string &ca_certificate_file,
                              string *response_body,
                              long *response_code,
                              string *error_description) {
   if (response_code != NULL)
     *response_code = 0;
 
+  if (!CheckParameters(parameters))
+    return false;
+
   // We may have been linked statically; if curl_easy_init is in the
   // current binary, no need to search for a dynamic version.
   void* curl_lib = dlopen(NULL, RTLD_NOW);
   if (!CheckCurlLib(curl_lib)) {
     fprintf(stderr,
             "Failed to open curl lib from binary, use libcurl.so instead\n");
     dlerror();  // Clear dlerror before attempting to open libraries.
     dlclose(curl_lib);
@@ -125,24 +128,24 @@ bool HTTPUpload::SendRequest(const strin
   if (!ca_certificate_file.empty())
     (*curl_easy_setopt)(curl, CURLOPT_CAINFO, ca_certificate_file.c_str());
 
   struct curl_httppost *formpost = NULL;
   struct curl_httppost *lastptr = NULL;
   // Add form data.
   CURLFORMcode (*curl_formadd)(struct curl_httppost **, struct curl_httppost **, ...);
   *(void**) (&curl_formadd) = dlsym(curl_lib, "curl_formadd");
-  (*curl_formadd)(&formpost, &lastptr, CURLFORM_COPYNAME, "extra",
-                  CURLFORM_BUFFER, "extra.json", CURLFORM_BUFFERPTR,
-                  parameters.c_str(), CURLFORM_BUFFERLENGTH,
-                  parameters.length(), CURLFORM_CONTENTTYPE, "application/json",
-                  CURLFORM_END);
+  map<string, string>::const_iterator iter = parameters.begin();
+  for (; iter != parameters.end(); ++iter)
+    (*curl_formadd)(&formpost, &lastptr,
+                 CURLFORM_COPYNAME, iter->first.c_str(),
+                 CURLFORM_COPYCONTENTS, iter->second.c_str(),
+                 CURLFORM_END);
 
   // Add form files.
-  map<string, string>::const_iterator iter = files.begin();
   for (iter = files.begin(); iter != files.end(); ++iter) {
     (*curl_formadd)(&formpost, &lastptr,
                  CURLFORM_COPYNAME, iter->first.c_str(),
                  CURLFORM_FILE, iter->second.c_str(),
                  CURLFORM_END);
   }
 
   (*curl_easy_setopt)(curl, CURLOPT_HTTPPOST, formpost);
@@ -202,9 +205,26 @@ bool HTTPUpload::SendRequest(const strin
 
 // static
 bool HTTPUpload::CheckCurlLib(void* curl_lib) {
   return curl_lib &&
       dlsym(curl_lib, "curl_easy_init") &&
       dlsym(curl_lib, "curl_easy_setopt");
 }
 
+// static
+bool HTTPUpload::CheckParameters(const map<string, string> &parameters) {
+  for (map<string, string>::const_iterator pos = parameters.begin();
+       pos != parameters.end(); ++pos) {
+    const string &str = pos->first;
+    if (str.size() == 0)
+      return false;  // disallow empty parameter names
+    for (unsigned int i = 0; i < str.size(); ++i) {
+      int c = str[i];
+      if (c < 32 || c == '"' || c > 127) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 }  // namespace google_breakpad
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/http_upload.h
@@ -24,17 +24,17 @@
 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 // HTTPUpload provides a "nice" API to send a multipart HTTP(S) POST
 // request using libcurl.  It currently supports requests that contain
-// parameters encoded in a JSON string, and a file to upload.
+// a set of string parameters (key/value pairs), and a file to upload.
 
 #ifndef COMMON_LINUX_HTTP_UPLOAD_H__
 #define COMMON_LINUX_HTTP_UPLOAD_H__
 
 #include <map>
 #include <string>
 
 #include "common/using_std_string.h"
@@ -44,35 +44,41 @@ namespace google_breakpad {
 using std::map;
 
 class HTTPUpload {
  public:
   // Sends the given sets of parameters and files as a multipart POST
   // request to the given URL.
   // Each key in |files| is the name of the file part of the request
   // (i.e. it corresponds to the name= attribute on an <input type="file">.
-  // Parameters are specified as a JSON-encoded string in |parameters|.
+  // Parameter names must contain only printable ASCII characters,
+  // and may not contain a quote (") character.
   // Only HTTP(S) URLs are currently supported.  Returns true on success.
   // If the request is successful and response_body is non-NULL,
   // the response body will be returned in response_body.
   // If response_code is non-NULL, it will be set to the HTTP response code
   // received (or 0 if the request failed before getting an HTTP response).
   // If the send fails, a description of the error will be
   // returned in error_description.
   static bool SendRequest(const string &url,
-                          const string &parameters,
+                          const map<string, string> &parameters,
                           const map<string, string> &files,
                           const string &proxy,
                           const string &proxy_user_pwd,
                           const string &ca_certificate_file,
                           string *response_body,
                           long *response_code,
                           string *error_description);
 
  private:
+  // Checks that the given list of parameters has only printable
+  // ASCII characters in the parameter name, and does not contain
+  // any quote (") characters.  Returns true if so.
+  static bool CheckParameters(const map<string, string> &parameters);
+
   // Checks the curl_lib parameter points to a valid curl lib.
   static bool CheckCurlLib(void* curl_lib);
 
   // No instances of this class should be created.
   // Disallow all constructors, destructors, and operator=.
   HTTPUpload();
   explicit HTTPUpload(const HTTPUpload &);
   void operator=(const HTTPUpload &);
--- a/toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.h
+++ b/toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.h
@@ -32,28 +32,28 @@
 // Each file is sent with a name field in addition to the filename and data
 // The data will be sent synchronously.
 
 #import <Foundation/Foundation.h>
 
 @interface HTTPMultipartUpload : NSObject {
  @protected
   NSURL *url_;                  // The destination URL (STRONG)
-  NSMutableString *parameters_;  // The JSON payload for sending data (STRONG)
+  NSDictionary *parameters_;    // The key/value pairs for sending data (STRONG)
   NSMutableDictionary *files_;  // Dictionary of name/file-path (STRONG)
   NSString *boundary_;          // The boundary string (STRONG)
   NSHTTPURLResponse *response_; // The response from the send (STRONG)
 }
 
 - (id)initWithURL:(NSURL *)url;
 
 - (NSURL *)URL;
 
-- (void)setParameters:(NSMutableString *)parameters;
-- (NSMutableString *)parameters;
+- (void)setParameters:(NSDictionary *)parameters;
+- (NSDictionary *)parameters;
 
 - (void)addFileAtPath:(NSString *)path name:(NSString *)name;
 - (void)addFileContents:(NSData *)data name:(NSString *)name;
 - (NSDictionary *)files;
 
 // Set the data and return the response
 - (NSData *)send:(NSError **)error;
 - (NSHTTPURLResponse *)response;
--- a/toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.m
+++ b/toolkit/crashreporter/google-breakpad/src/common/mac/HTTPMultipartUpload.m
@@ -88,43 +88,40 @@ static NSData *SendSynchronousNSURLReque
                                returningResponse:out_response
                                            error:out_error];
 #endif
 }
 @interface HTTPMultipartUpload(PrivateMethods)
 - (NSString *)multipartBoundary;
 // Each of the following methods will append the starting multipart boundary,
 // but not the ending one.
-- (NSData *)formDataForJSON:(NSString *)json;
+- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value;
 - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name;
 - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name;
 @end
 
 @implementation HTTPMultipartUpload
 //=============================================================================
 #pragma mark -
 #pragma mark || Private ||
 //=============================================================================
 - (NSString *)multipartBoundary {
   // The boundary has 27 '-' characters followed by 16 hex digits
   return [NSString stringWithFormat:@"---------------------------%08X%08X",
     rand(), rand()];
 }
 
 //=============================================================================
-- (NSData *)formDataForJSON:(NSString *)json {
-  NSMutableData *data = [NSMutableData data];
-  NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"extra\"; "
-                   "filename=\"extra.json\"\r\nContent-Type: application/json\r\n\r\n";
-  NSString *form = [NSString stringWithFormat:fmt, boundary_];
+- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value {
+  NSString *escaped = PercentEncodeNSString(key);
+  NSString *fmt =
+    @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n";
+  NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value];
 
-  [data appendData:[form dataUsingEncoding:NSUTF8StringEncoding]];
-  [data appendData:[json dataUsingEncoding:NSUTF8StringEncoding]];
-
-  return data;
+  return [form dataUsingEncoding:NSUTF8StringEncoding];
 }
 
 //=============================================================================
 - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name {
   NSMutableData *data = [NSMutableData data];
   NSString *escaped = PercentEncodeNSString(name);
   NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; "
     "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n";
@@ -169,25 +166,25 @@ static NSData *SendSynchronousNSURLReque
 }
 
 //=============================================================================
 - (NSURL *)URL {
   return url_;
 }
 
 //=============================================================================
-- (void)setParameters:(NSMutableString *)parameters {
+- (void)setParameters:(NSDictionary *)parameters {
   if (parameters != parameters_) {
     [parameters_ release];
-    parameters_ = [parameters mutableCopy];
+    parameters_ = [parameters copy];
   }
 }
 
 //=============================================================================
-- (NSMutableString *)parameters {
+- (NSDictionary *)parameters {
   return parameters_;
 }
 
 //=============================================================================
 - (void)addFileAtPath:(NSString *)path name:(NSString *)name {
   [files_ setObject:path forKey:name];
 }
 
@@ -208,18 +205,26 @@ static NSData *SendSynchronousNSURLReque
           initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy
       timeoutInterval:10.0 ];
 
   NSMutableData *postBody = [NSMutableData data];
 
   [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",
     boundary_] forHTTPHeaderField:@"Content-type"];
 
-  // Add JSON parameters to the message
-  [postBody appendData:[self formDataForJSON:parameters_]];
+  // Add any parameters to the message
+  NSArray *parameterKeys = [parameters_ allKeys];
+  NSString *key;
+
+  NSInteger count = [parameterKeys count];
+  for (NSInteger i = 0; i < count; ++i) {
+    key = [parameterKeys objectAtIndex:i];
+    [postBody appendData:[self formDataForKey:key
+                                        value:[parameters_ objectForKey:key]]];
+  }
 
   // Add any files to the message
   NSArray *fileNames = [files_ allKeys];
   for (NSString *name in fileNames) {
     id fileOrData = [files_ objectForKey:name];
     NSData *fileData;
 
     // The object can be either the path to a file (NSString) or the contents
--- a/toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.cc
@@ -58,25 +58,30 @@ class HTTPUpload::AutoInternetHandle {
   HINTERNET get() { return handle_; }
 
  private:
   HINTERNET handle_;
 };
 
 // static
 bool HTTPUpload::SendRequest(const wstring &url,
-                             const string &parameters,
+                             const map<wstring, wstring> &parameters,
                              const map<wstring, wstring> &files,
                              int *timeout,
                              wstring *response_body,
                              int *response_code) {
   if (response_code) {
     *response_code = 0;
   }
 
+  // TODO(bryner): support non-ASCII parameter names
+  if (!CheckParameters(parameters)) {
+    return false;
+  }
+
   // Break up the URL and make sure we can handle it
   wchar_t scheme[16], host[256], path[256];
   URL_COMPONENTS components;
   memset(&components, 0, sizeof(components));
   components.dwStructSize = sizeof(components);
   components.lpszScheme = scheme;
   components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]);
   components.lpszHostName = host;
@@ -255,40 +260,35 @@ wstring HTTPUpload::GenerateMultipartBou
 // static
 wstring HTTPUpload::GenerateRequestHeader(const wstring &boundary) {
   wstring header = L"Content-Type: multipart/form-data; boundary=";
   header += boundary;
   return header;
 }
 
 // static
-bool HTTPUpload::GenerateRequestBody(const string &parameters,
+bool HTTPUpload::GenerateRequestBody(const map<wstring, wstring> &parameters,
                                      const map<wstring, wstring> &files,
                                      const wstring &boundary,
                                      string *request_body) {
   string boundary_str = WideToUTF8(boundary);
   if (boundary_str.empty()) {
     return false;
   }
 
   request_body->clear();
 
-  // Append the extra data as a single JSON form entry
-  request_body->append("--" + boundary_str + "\r\n");
-  request_body->append(
-      "Content-Disposition: form-data; "
-      "name=\"extra\"; "
-      "filename=\"extra.json\"\r\n");
-  request_body->append("Content-Type: application/json\r\n");
-  request_body->append("\r\n");
-
-  if (!parameters.empty()) {
-    request_body->append(parameters);
+  // Append each of the parameter pairs as a form-data part
+  for (map<wstring, wstring>::const_iterator pos = parameters.begin();
+       pos != parameters.end(); ++pos) {
+    request_body->append("--" + boundary_str + "\r\n");
+    request_body->append("Content-Disposition: form-data; name=\"" +
+                         WideToUTF8(pos->first) + "\"\r\n\r\n" +
+                         WideToUTF8(pos->second) + "\r\n");
   }
-  request_body->append("\r\n");
 
   for (map<wstring, wstring>::const_iterator pos = files.begin();
        pos != files.end(); ++pos) {
     vector<char> contents;
     if (!GetFileContents(pos->second, &contents)) {
       return false;
     }
 
@@ -394,9 +394,27 @@ string HTTPUpload::WideToMBCP(const wstr
   WideCharToMultiByte(cp, 0, wide.c_str(), -1, buf, charcount,
                       NULL, NULL);
 
   string result(buf);
   delete[] buf;
   return result;
 }
 
+// static
+bool HTTPUpload::CheckParameters(const map<wstring, wstring> &parameters) {
+  for (map<wstring, wstring>::const_iterator pos = parameters.begin();
+       pos != parameters.end(); ++pos) {
+    const wstring &str = pos->first;
+    if (str.size() == 0) {
+      return false;  // disallow empty parameter names
+    }
+    for (unsigned int i = 0; i < str.size(); ++i) {
+      wchar_t c = str[i];
+      if (c < 32 || c == '"' || c > 127) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 }  // namespace google_breakpad
--- a/toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.h
+++ b/toolkit/crashreporter/google-breakpad/src/common/windows/http_upload.h
@@ -24,17 +24,17 @@
 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 // HTTPUpload provides a "nice" API to send a multipart HTTP(S) POST
 // request using wininet.  It currently supports requests that contain
-// parameters encoded in a JSON string, and a file to upload.
+// a set of string parameters (key/value pairs), and a file to upload.
 
 #ifndef COMMON_WINDOWS_HTTP_UPLOAD_H_
 #define COMMON_WINDOWS_HTTP_UPLOAD_H_
 
 #pragma warning(push)
 // Disable exception handler warnings.
 #pragma warning(disable : 4530)
 
@@ -42,35 +42,36 @@
 #include <wininet.h>
 
 #include <map>
 #include <string>
 #include <vector>
 
 namespace google_breakpad {
 
+using std::string;
+using std::wstring;
 using std::map;
-using std::string;
 using std::vector;
-using std::wstring;
 
 class HTTPUpload {
  public:
   // Sends the given sets of parameters and files as a multipart POST
   // request to the given URL.
   // Each key in |files| is the name of the file part of the request
   // (i.e. it corresponds to the name= attribute on an <input type="file">.
-  // Parameters are specified as a JSON-encoded string in |parameters|.
+  // Parameter names must contain only printable ASCII characters,
+  // and may not contain a quote (") character.
   // Only HTTP(S) URLs are currently supported.  Returns true on success.
   // If the request is successful and response_body is non-NULL,
   // the response body will be returned in response_body.
   // If response_code is non-NULL, it will be set to the HTTP response code
   // received (or 0 if the request failed before getting an HTTP response).
   static bool SendRequest(const wstring &url,
-                          const string &parameters,
+                          const map<wstring, wstring> &parameters,
                           const map<wstring, wstring> &files,
                           int *timeout,
                           wstring *response_body,
                           int *response_code);
 
  private:
   class AutoInternetHandle;
 
@@ -81,20 +82,20 @@ class HTTPUpload {
   static bool ReadResponse(HINTERNET request, wstring* response);
 
   // Generates a new multipart boundary for a POST request
   static wstring GenerateMultipartBoundary();
 
   // Generates a HTTP request header for a multipart form submit.
   static wstring GenerateRequestHeader(const wstring &boundary);
 
-  // Given a parameter string, a set of upload files, and a file part name,
+  // Given a set of parameters, a set of upload files, and a file part name,
   // generates a multipart request body string with these parameters
   // and minidump contents.  Returns true on success.
-  static bool GenerateRequestBody(const string &parameters,
+  static bool GenerateRequestBody(const map<wstring, wstring> &parameters,
                                   const map<wstring, wstring> &files,
                                   const wstring &boundary,
                                   string *request_body);
 
   // Fills the supplied vector with the contents of filename.
   static bool GetFileContents(const wstring &filename, vector<char> *contents);
 
   // Converts a UTF8 string to UTF16.
@@ -103,16 +104,21 @@ class HTTPUpload {
   // Converts a UTF16 string to UTF8.
   static string WideToUTF8(const wstring &wide) {
       return WideToMBCP(wide, CP_UTF8);
   }
 
   // Converts a UTF16 string to specified code page.
   static string WideToMBCP(const wstring &wide, unsigned int cp);
 
+  // Checks that the given list of parameters has only printable
+  // ASCII characters in the parameter name, and does not contain
+  // any quote (") characters.  Returns true if so.
+  static bool CheckParameters(const map<wstring, wstring> &parameters);
+
   // No instances of this class should be created.
   // Disallow all constructors, destructors, and operator=.
   HTTPUpload();
   explicit HTTPUpload(const HTTPUpload &);
   void operator=(const HTTPUpload &);
   ~HTTPUpload();
 };
 
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -66,17 +66,16 @@ using google_breakpad::CodeModule;
 using google_breakpad::CodeModules;
 using google_breakpad::Minidump;
 using google_breakpad::MinidumpProcessor;
 using google_breakpad::PathnameStripper;
 using google_breakpad::ProcessResult;
 using google_breakpad::ProcessState;
 using google_breakpad::StackFrame;
 
-using mozilla::IFStream;
 using mozilla::OFStream;
 using mozilla::Unused;
 
 MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
 
 // Path of the minidump to be analyzed.
 static string gMinidumpPath;
 
@@ -356,72 +355,52 @@ static bool ProcessMinidump(Json::Value&
   aStackTraces["status"] = ResultString(rv);
 
   ConvertProcessStateToJSON(processState, aStackTraces, aFullStacks,
                             aCertSubjects);
 
   return true;
 }
 
-static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
-  IFStream f(
-#if defined(XP_WIN)
-      UTF8ToWide(aExtraDataPath).c_str(),
-#else
-      aExtraDataPath.c_str(),
-#endif  // defined(XP_WIN)
-      ios::in);
-  if (!f.is_open()) {
-    return false;
-  }
-
-  Json::CharReaderBuilder builder;
-  return parseFromStream(builder, f, &aExtra, nullptr);
-}
-
 // Update the extra data file by adding the StackTraces and ModuleSignatureInfo
 // fields that contain the JSON outputs of this program.
 static bool UpdateExtraDataFile(const string& aDumpPath,
                                 const Json::Value& aStackTraces,
                                 const Json::Value& aCertSubjects) {
   string extraDataPath(aDumpPath);
   int dot = extraDataPath.rfind('.');
 
   if (dot < 0) {
     return false;  // Not a valid dump path
   }
 
   extraDataPath.replace(dot, extraDataPath.length() - dot, kExtraDataExtension);
+  bool res = false;
 
-  Json::Value extra;
-  if (!ReadExtraFile(extraDataPath, extra)) {
-    return false;
-  }
-
+  // We want to open the extra file in append mode.
+  ios_base::openmode mode = ios::out | ios::app;
   OFStream f(
 #if defined(XP_WIN)
       UTF8ToWide(extraDataPath).c_str(),
 #else
       extraDataPath.c_str(),
 #endif  // defined(XP_WIN)
-      ios::out | ios::trunc);
+      mode);
 
-  bool res = false;
   if (f.is_open()) {
-    extra["StackTraces"] = aStackTraces;
+    Json::FastWriter writer;
+
+    f << "StackTraces=" << writer.write(aStackTraces);
+    res = !f.fail();
+
     if (!!aCertSubjects) {
-      extra["ModuleSignatureInfo"] = aCertSubjects;
+      f << "ModuleSignatureInfo=" << writer.write(aCertSubjects);
+      res &= !f.fail();
     }
 
-    Json::StreamWriterBuilder builder;
-    builder["indentation"] = "";
-    std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
-    writer->write(extra, &f);
-    f << "\n";
-    res = !f.fail();
     f.close();
   }
 
   return res;
 }
 
 bool GenerateStacks(const string& aDumpPath, const bool aFullStacks) {
   Json::Value stackTraces;
--- a/toolkit/crashreporter/moz.build
+++ b/toolkit/crashreporter/moz.build
@@ -87,16 +87,17 @@ if CONFIG['MOZ_CRASHREPORTER']:
     if CONFIG['OS_ARCH'] == 'Darwin':
         UNIFIED_SOURCES += [
             'mac_utils.mm',
         ]
 
     EXTRA_JS_MODULES += [
         'CrashReports.jsm',
         'CrashSubmit.jsm',
+        'KeyValueParser.jsm',
     ]
 
     include('/ipc/chromium/chromium-config.mozbuild')
 
     if CONFIG['OS_TARGET'] == 'Android':
         DEFINES['ANDROID_NDK_MAJOR_VERSION'] = CONFIG['ANDROID_NDK_MAJOR_VERSION']
         DEFINES['ANDROID_NDK_MINOR_VERSION'] = CONFIG['ANDROID_NDK_MINOR_VERSION']
         DEFINES['ANDROID_PACKAGE_NAME'] = '"%s"' % CONFIG['ANDROID_PACKAGE_NAME']
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -176,17 +176,17 @@ typedef std::string xpstring;
 #ifndef XP_LINUX
 static const XP_CHAR dumpFileExtension[] = XP_TEXT(".dmp");
 #endif
 
 static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra");
 static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz");
 static xpstring* defaultMemoryReportPath = nullptr;
 
-static const char kCrashMainID[] = "crash.main.3\n";
+static const char kCrashMainID[] = "crash.main.2\n";
 
 static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
 
 static XP_CHAR* pendingDirectory;
 static XP_CHAR* crashReporterPath;
 static XP_CHAR* memoryReportPath;
 #ifdef XP_MACOSX
 static XP_CHAR* libraryPath;  // Path where the NSS library is
@@ -522,153 +522,147 @@ bool copy_file(const char* from, const c
 
   return ok;
 }
 #endif
 
 /**
  * The PlatformWriter class provides a tool to create and write to a file that
  * is safe to call from within an exception handler. To use it this way the
- * file path needs to be provided as a bare C string.
+ * file path needs to be provided as a bare C string. If the writer is created
+ * using an nsIFile instance it will *not* be safe to use from a crashed
+ * context.
  */
+#ifdef XP_WIN
+
 class PlatformWriter {
  public:
-#ifdef XP_WIN
-  typedef HANDLE NativeFileDesc;
-  typedef wchar_t NativeChar;
-#elif defined(XP_UNIX)
-  typedef int NativeFileDesc;
-  typedef char NativeChar;
-#else
-#  error "Need implementation of PlatformWriter for this platform"
-#endif
-
-  const NativeFileDesc kInvalidFileDesc =
-#ifdef XP_WIN
-      INVALID_HANDLE_VALUE;
-#elif defined(XP_UNIX)
-      -1;
-#endif
-
-  PlatformWriter() : mFD(kInvalidFileDesc) {}
-  explicit PlatformWriter(const NativeChar* aPath) : PlatformWriter() {
-    Open(aPath);
+  PlatformWriter() : mHandle(INVALID_HANDLE_VALUE) {}
+
+  explicit PlatformWriter(const wchar_t* path) : PlatformWriter() {
+    Open(path);
+  }
+
+  explicit PlatformWriter(nsIFile* file) : PlatformWriter() {
+    nsAutoString path;
+    if (NS_SUCCEEDED(file->GetPath(path))) {
+      Open(path.get());
+    }
   }
 
   ~PlatformWriter() {
     if (Valid()) {
-#ifdef XP_WIN
-      CloseHandle(mFD);
-#elif defined(XP_UNIX)
-      sys_close(mFD);
-#endif
+      CloseHandle(mHandle);
     }
   }
 
-  void Open(const NativeChar* aPath) {
-#ifdef XP_WIN
-    mFD = CreateFile(aPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
-                     FILE_ATTRIBUTE_NORMAL, nullptr);
-#elif defined(XP_UNIX)
-    mFD = sys_open(aPath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
-#endif
+  void Open(const wchar_t* path) {
+    mHandle = CreateFile(path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
+                         FILE_ATTRIBUTE_NORMAL, nullptr);
   }
 
-  void OpenHandle(NativeFileDesc aFD) { mFD = aFD; }
-  bool Valid() { return mFD != kInvalidFileDesc; }
-
-  void WriteBuffer(const char* aBuffer, size_t aLen) {
+  void OpenHandle(HANDLE aHandle) { mHandle = aHandle; }
+
+  bool Valid() { return mHandle != INVALID_HANDLE_VALUE; }
+
+  void WriteBuffer(const char* buffer, size_t len) {
     if (!Valid()) {
       return;
     }
-#ifdef XP_WIN
     DWORD nBytes;
-    WriteFile(mFD, aBuffer, aLen, &nBytes, nullptr);
-#elif defined(XP_UNIX)
-    mozilla::Unused << sys_write(mFD, aBuffer, aLen);
-#endif
+    WriteFile(mHandle, buffer, len, &nBytes, nullptr);
   }
 
-  void WriteString(const char* aStr) { WriteBuffer(aStr, my_strlen(aStr)); }
-
-  template <int N>
-  void WriteLiteral(const char (&aStr)[N]) {
-    WriteBuffer(aStr, N - 1);
-  }
-
-  NativeFileDesc FileDesc() { return mFD; }
+  HANDLE Handle() { return mHandle; }
 
  private:
-  NativeFileDesc mFD;
+  HANDLE mHandle;
 };
 
-class JSONAnnotationWriter : public AnnotationWriter {
+#elif defined(XP_UNIX)
+
+class PlatformWriter {
  public:
-  explicit JSONAnnotationWriter(PlatformWriter& aPlatformWriter)
-      : mWriter(aPlatformWriter), mEmpty(true) {
-    mWriter.WriteBuffer("{", 1);
+  PlatformWriter() : mFD(-1) {}
+
+  explicit PlatformWriter(const char* path) : PlatformWriter() { Open(path); }
+
+  explicit PlatformWriter(nsIFile* file) : PlatformWriter() {
+    nsAutoCString path;
+    if (NS_SUCCEEDED(file->GetNativePath(path))) {
+      Open(path.get());
+    }
+  }
+
+  ~PlatformWriter() {
+    if (Valid()) {
+      sys_close(mFD);
+    }
+  }
+
+  void Open(const char* path) {
+    mFD = sys_open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
   }
 
-  ~JSONAnnotationWriter() { mWriter.WriteBuffer("}", 1); }
-
-  void Write(Annotation aAnnotation, const char* aValue,
-             size_t aLen = 0) override {
-    size_t len = aLen ? aLen : my_strlen(aValue);
-    const char* annotationStr = AnnotationToString(aAnnotation);
-
-    WritePrefix();
-    mWriter.WriteBuffer(annotationStr, my_strlen(annotationStr));
-    WriteSeparator();
-    WriteEscapedString(aValue, len);
-    WriteSuffix();
+  void OpenHandle(int aFd) { mFD = aFd; }
+
+  bool Valid() { return mFD != -1; }
+
+  void WriteBuffer(const char* buffer, size_t len) {
+    if (!Valid()) {
+      return;
+    }
+    Unused << sys_write(mFD, buffer, len);
+  }
+
+ private:
+  int mFD;
+};
+
+#else
+#  error "Need implementation of PlatformWrite for this platform"
+#endif
+
+template <int N>
+static void WriteLiteral(PlatformWriter& pw, const char (&str)[N]) {
+  pw.WriteBuffer(str, N - 1);
+}
+
+static void WriteString(PlatformWriter& pw, const char* str) {
+  pw.WriteBuffer(str, my_strlen(str));
+}
+
+class AnnotationWriter {
+ public:
+  virtual void Write(Annotation aAnnotation, const char* aValue) = 0;
+};
+
+class INIAnnotationWriter : public AnnotationWriter {
+ public:
+  explicit INIAnnotationWriter(PlatformWriter& aPlatformWriter)
+      : mPlatformWriter(aPlatformWriter) {}
+
+  void Write(Annotation aAnnotation, const char* aValue) override {
+    WriteString(mPlatformWriter, AnnotationToString(aAnnotation));
+    WriteLiteral(mPlatformWriter, "=");
+    WriteString(mPlatformWriter, aValue);
+    WriteLiteral(mPlatformWriter, "\n");
   };
 
  private:
-  void WritePrefix() {
-    if (mEmpty) {
-      mWriter.WriteBuffer("\"", 1);
-      mEmpty = false;
-    } else {
-      mWriter.WriteBuffer(",\"", 2);
-    }
-  }
-
-  void WriteSeparator() { mWriter.WriteBuffer("\":\"", 3); }
-  void WriteSuffix() { mWriter.WriteBuffer("\"", 1); }
-  void WriteEscapedString(const char* aStr, size_t aLen) {
-    for (size_t i = 0; i < aLen; i++) {
-      uint8_t c = aStr[i];
-      if (c <= 0x1f || c == '\\' || c == '\"') {
-        mWriter.WriteBuffer("\\u00", 4);
-        WriteHexDigitAsAsciiChar((c & 0x00f0) >> 4);
-        WriteHexDigitAsAsciiChar(c & 0x000f);
-      } else {
-        mWriter.WriteBuffer(aStr + i, 1);
-      }
-    }
-  }
-
-  void WriteHexDigitAsAsciiChar(uint8_t u) {
-    char buf[1];
-    buf[0] = static_cast<unsigned>((u < 10) ? '0' + u : 'a' + (u - 10));
-    mWriter.WriteBuffer(buf, 1);
-  }
-
-  PlatformWriter mWriter;
-  bool mEmpty;
+  PlatformWriter& mPlatformWriter;
 };
 
 class BinaryAnnotationWriter : public AnnotationWriter {
  public:
   explicit BinaryAnnotationWriter(PlatformWriter& aPlatformWriter)
       : mPlatformWriter(aPlatformWriter) {}
 
-  void Write(Annotation aAnnotation, const char* aValue,
-             size_t aLen = 0) override {
-    uint64_t len = aLen ? aLen : my_strlen(aValue);
+  void Write(Annotation aAnnotation, const char* aValue) override {
+    uint64_t len = my_strlen(aValue);
     mPlatformWriter.WriteBuffer((const char*)&aAnnotation, sizeof(aAnnotation));
     mPlatformWriter.WriteBuffer((const char*)&len, sizeof(len));
     mPlatformWriter.WriteBuffer(aValue, len);
   };
 
  private:
   PlatformWriter& mPlatformWriter;
 };
@@ -918,30 +912,56 @@ static bool LaunchCrashHandlerService(XP
     Unused << HANDLE_EINTR(sys_waitpid(pid, &status, __WALL));
   }
 
   return true;
 }
 
 #endif
 
+static void WriteEscapedMozCrashReason(PlatformWriter& aWriter) {
+  if (gMozCrashReason == nullptr) {
+    return;  // No crash reason, bail out
+  }
+
+  const char* reason = gMozCrashReason;
+  size_t len = my_strlen(gMozCrashReason);
+
+  WriteString(aWriter, AnnotationToString(Annotation::MozCrashReason));
+  WriteLiteral(aWriter, "=");
+
+  // The crash reason might not be escaped so escape it one character at a time
+  // and write out the resulting string.
+  for (size_t i = 0; i < len; i++) {
+    if (reason[i] == '\\') {
+      WriteLiteral(aWriter, "\\\\");
+    } else if (reason[i] == '\n') {
+      WriteLiteral(aWriter, "\\n");
+    } else {
+      aWriter.WriteBuffer(reason + i, 1);
+    }
+  }
+
+  WriteLiteral(aWriter, "\n");
+}
+
 static void WriteMozCrashReason(AnnotationWriter& aWriter) {
   if (gMozCrashReason != nullptr) {
     aWriter.Write(Annotation::MozCrashReason, gMozCrashReason);
   }
 }
 
 static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
                                                 const phc::AddrInfo* addrInfo,
                                                 time_t crashTime) {
-  JSONAnnotationWriter writer(pw);
+  INIAnnotationWriter writer(pw);
   for (auto key : MakeEnumeratedRange(Annotation::Count)) {
     const nsCString& value = crashReporterAPIData_Table[key];
     if (!value.IsEmpty()) {
-      writer.Write(key, value.get(), value.Length());
+      writer.Write(key, value.get());
     }
   }
 
   if (currentSessionId) {
     writer.Write(Annotation::TelemetrySessionId, currentSessionId);
   }
 
   char crashTimeString[32];
@@ -979,23 +999,22 @@ static void WriteAnnotationsForMainProce
   if (gBreakpadReservedVM) {
     _ui64toa(uintptr_t(gBreakpadReservedVM), buffer, 10);
     writer.Write(Annotation::BreakpadReserveAddress, buffer);
     _ui64toa(kReserveSize, buffer, 10);
     writer.Write(Annotation::BreakpadReserveSize, buffer);
   }
 
 #  ifdef HAS_DLL_BLOCKLIST
-  // HACK: The DLL blocklist code will manually write its annotations as JSON
-  DllBlocklist_WriteNotes(writer);
+  DllBlocklist_WriteNotes(pw.Handle());
 #  endif
 #endif  // XP_WIN
 
   WriteMemoryStatus(writer);
-  WriteMozCrashReason(writer);
+  WriteEscapedMozCrashReason(pw);
 
   char oomAllocationSizeBuffer[32] = "";
   if (gOOMAllocationSize) {
     XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer);
     writer.Write(Annotation::OOMAllocationSize, oomAllocationSizeBuffer);
   }
 
   char texturesSizeBuffer[32] = "";
@@ -1055,21 +1074,21 @@ static void WriteCrashEventFile(time_t c
     p = Concat(p, XP_PATH_SEPARATOR, &size);
 #ifdef XP_LINUX
     Concat(p, id_ascii, &size);
 #else
     Concat(p, minidump_id, &size);
 #endif
 
     eventFile.Open(crashEventPath);
-    eventFile.WriteLiteral(kCrashMainID);
-    eventFile.WriteString(crashTimeString);
-    eventFile.WriteLiteral("\n");
-    eventFile.WriteString(id_ascii);
-    eventFile.WriteLiteral("\n");
+    WriteLiteral(eventFile, kCrashMainID);
+    WriteString(eventFile, crashTimeString);
+    WriteLiteral(eventFile, "\n");
+    WriteString(eventFile, id_ascii);
+    WriteLiteral(eventFile, "\n");
     WriteAnnotationsForMainProcessCrash(eventFile, addrInfo, crashTime);
   }
 }
 
 // Callback invoked from breakpad's exception handler, this writes out the
 // last annotations after a crash occurs and launches the crash reporter client.
 //
 // This function is not declared static even though it's not used outside of
@@ -1130,17 +1149,17 @@ bool MinidumpCallback(
   crashTime = time(nullptr);
 #endif
   char crashTimeString[32];
   XP_TTOA(crashTime, crashTimeString);
 
   // write crash time to file
   if (lastCrashTimeFilename[0] != 0) {
     PlatformWriter lastCrashFile(lastCrashTimeFilename);
-    lastCrashFile.WriteString(crashTimeString);
+    WriteString(lastCrashFile, crashTimeString);
   }
 
   WriteCrashEventFile(crashTime, crashTimeString, addrInfo,
 #ifdef XP_LINUX
                       descriptor
 #else
                       minidump_id
 #endif
@@ -1280,17 +1299,18 @@ static void PrepareChildExceptionTimeAnn
   f = static_cast<HANDLE>(context);
 #else
   f = GetAnnotationTimeCrashFd();
 #endif
   PlatformWriter apiData;
   apiData.OpenHandle(f);
   BinaryAnnotationWriter writer(apiData);
 
-  // ...and write out any annotations.
+  // ...and write out any annotations. These must be escaped if necessary
+  // (but don't call EscapeAnnotation here, because it touches the heap).
   WriteMemoryStatus(writer);
 
   char oomAllocationSizeBuffer[32] = "";
   if (gOOMAllocationSize) {
     XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer);
     writer.Write(Annotation::OOMAllocationSize, oomAllocationSizeBuffer);
   }
 
@@ -1969,16 +1989,50 @@ nsresult UnsetExceptionHandler() {
   delete dumpSafetyLock;
   dumpSafetyLock = nullptr;
 
   std::set_terminate(oldTerminateHandler);
 
   return NS_OK;
 }
 
+static void ReplaceChar(nsCString& str, const nsACString& character,
+                        const nsACString& replacement) {
+  nsCString::const_iterator iter, end;
+
+  str.BeginReading(iter);
+  str.EndReading(end);
+
+  while (FindInReadable(character, iter, end)) {
+    nsCString::const_iterator start;
+    str.BeginReading(start);
+    int32_t pos = end - start;
+    str.Replace(pos - 1, 1, replacement);
+
+    str.BeginReading(iter);
+    iter.advance(pos + replacement.Length() - 1);
+    str.EndReading(end);
+  }
+}
+
+static nsresult EscapeAnnotation(const nsACString& data,
+                                 nsCString& escapedData) {
+  if (FindInReadable(NS_LITERAL_CSTRING("\0"), data))
+    return NS_ERROR_INVALID_ARG;
+
+  escapedData = data;
+
+  // escape backslashes
+  ReplaceChar(escapedData, NS_LITERAL_CSTRING("\\"),
+              NS_LITERAL_CSTRING("\\\\"));
+  // escape newlines
+  ReplaceChar(escapedData, NS_LITERAL_CSTRING("\n"), NS_LITERAL_CSTRING("\\n"));
+  return NS_OK;
+}
+
 class DelayedNote {
  public:
   DelayedNote(Annotation aKey, const nsACString& aData)
       : mKey(aKey), mData(aData), mType(CrashAnnotation) {}
 
   explicit DelayedNote(const nsACString& aData)
       : mData(aData), mType(AppNote) {}
 
@@ -2030,33 +2084,38 @@ nsresult AnnotateCrashReport(Annotation 
   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(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(key, data);
+      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_Table[key] = data;
+
+  crashReporterAPIData_Table[key] = escapedData;
 
   return NS_OK;
 }
 
 nsresult RemoveCrashReportAnnotation(Annotation key) {
   return AnnotateCrashReport(key, EmptyCString());
 }
 
@@ -2113,20 +2172,30 @@ void SetEventloopNestingLevel(uint32_t l
 void SetMinidumpAnalysisAllThreads() {
   char* env = strdup("MOZ_CRASHREPORTER_DUMP_ALL_THREADS=1");
   PR_SetEnv(env);
 }
 
 nsresult AppendAppNotesToCrashReport(const nsACString& data) {
   if (!GetEnabled()) return NS_ERROR_NOT_INITIALIZED;
 
+  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(data, escapedData);
+    if (NS_FAILED(rv)) return rv;
+
     if (RefPtr<CrashReporterClient> client =
             CrashReporterClient::GetSingleton()) {
-      client->AppendAppNotes(data);
+      client->AppendAppNotes(escapedData);
       return NS_OK;
     }
 
     // EnqueueDelayedNote can only be called on the main thread.
     MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
     EnqueueDelayedNote(new DelayedNote(data));
     return NS_OK;
@@ -2782,54 +2851,51 @@ static void ReadAndValidateExceptionTime
       if (res != 1) {
         return;
       }
 
       len--;
       value.Append(c);
     } while (len > 0);
 
+    nsAutoCString escapedValue;
+    nsresult rv = EscapeAnnotation(value, escapedValue);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
     // Looks good, save the (annotation, value) pair
-    aAnnotations[static_cast<Annotation>(rawAnnotation)] = value;
+    aAnnotations[static_cast<Annotation>(rawAnnotation)] = escapedValue;
   } while (res > 0);
 }
 
 static bool WriteExtraFile(PlatformWriter pw,
                            const AnnotationTable& aAnnotations) {
   if (!pw.Valid()) {
     return false;
   }
 
-  JSONAnnotationWriter writer(pw);
+  INIAnnotationWriter writer(pw);
   for (auto key : MakeEnumeratedRange(Annotation::Count)) {
     const nsCString& value = aAnnotations[key];
     if (!value.IsEmpty()) {
-      writer.Write(key, value.get(), value.Length());
+      writer.Write(key, value.get());
     }
   }
 
   return true;
 }
 
 bool WriteExtraFile(const nsAString& id, const AnnotationTable& annotations) {
   nsCOMPtr<nsIFile> extra;
   if (!GetMinidumpLimboDir(getter_AddRefs(extra))) {
     return false;
   }
 
   extra->Append(id + NS_LITERAL_STRING(".extra"));
-#ifdef XP_WIN
-  nsAutoString path;
-  NS_ENSURE_SUCCESS(extra->GetPath(path), false);
-#elif defined(XP_UNIX)
-  nsAutoCString path;
-  NS_ENSURE_SUCCESS(extra->GetNativePath(path), false);
-#endif
-
-  return WriteExtraFile(PlatformWriter(path.get()), annotations);
+
+  return WriteExtraFile(PlatformWriter(extra), annotations);
 }
 
 static void ReadExceptionTimeAnnotations(AnnotationTable& aAnnotations,
                                          uint32_t aPid) {
   // Read exception-time annotations
   StaticMutexAutoLock pidMapLock(processMapLock);
   if (aPid && processToCrashFd.count(aPid)) {
     PRFileDesc* prFd = processToCrashFd[aPid];
--- a/toolkit/crashreporter/test/browser/crashreport.sjs
+++ b/toolkit/crashreporter/test/browser/crashreport.sjs
@@ -106,22 +106,17 @@ function parseMultipartForm(request)
       let bits = headers["Content-Disposition"].split(';');
       if (bits[0] == 'form-data') {
         for (let i = 0; i < bits.length; i++) {
           let b = bits[i].trimLeft();
           if (b.indexOf('name=') == 0) {
             //TODO: handle non-ascii here?
             let name = b.substring(6, b.length - 1);
             //TODO: handle multiple-value properties?
-            if (("Content-Type" in headers) &&
-                (headers["Content-Type"] == "application/json")) {
-              formData = Object.assign(formData, JSON.parse(part));
-            } else {
-              formData[name] = part;
-            }
+            formData[name] = part;
           }
           //TODO: handle filename= ?
           //TODO: handle multipart/mixed for multi-file uploads?
         }
       }
     }
   }
   return formData;
@@ -157,17 +152,17 @@ function handleRequest(request, response
 
     if (formData && 'upload_file_minidump' in formData) {
       response.setHeader("Content-Type", "text/plain", false);
 
       let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
         .getService(Ci.nsIUUIDGenerator);
       let uuid = uuidGenerator.generateUUID().toString();
       // ditch the {}, add bp- prefix
-      uuid = 'bp-' + uuid.substring(1,uuid.length-1);
+      uuid = 'bp-' + uuid.substring(1,uuid.length-2);
 
       let d = JSON.stringify(formData);
       //dump('saving crash report ' + uuid + ': ' + d + '\n');
       setState(uuid, d);
 
       response.write("CrashID=" + uuid + "\n");
     }
     else {
--- a/toolkit/crashreporter/test/browser/head.js
+++ b/toolkit/crashreporter/test/browser/head.js
@@ -135,17 +135,21 @@ function writeCrashReportFile(dir, uuid,
 }
 
 function writeMinidumpFile(dir, uuid, date) {
   // that's the start of a valid minidump, anyway
   writeCrashReportFile(dir, uuid, ".dmp", date, "MDMP");
 }
 
 function writeExtraFile(dir, uuid, date, data) {
-  writeCrashReportFile(dir, uuid, ".extra", date, JSON.stringify(data));
+  let extradata = "";
+  for (let x in data) {
+    extradata += x + "=" + data[x] + "\n";
+  }
+  writeCrashReportFile(dir, uuid, ".extra", date, extradata);
 }
 
 function writeMemoryReport(dir, uuid, date) {
   let data = "Let's pretend this is a memory report";
   writeCrashReportFile(dir, uuid, ".memory.json.gz", date, data);
 }
 
 function addPendingCrashreport(crD, date, extra) {
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -4,22 +4,16 @@ ChromeUtils.import("resource://testing-c
 var { AppConstants } = ChromeUtils.import(
   "resource://gre/modules/AppConstants.jsm"
 );
 
 function getEventDir() {
   return OS.Path.join(do_get_tempdir().path, "crash-events");
 }
 
-function sendCommandAsync(command) {
-  return new Promise(resolve => {
-    sendCommand(command, resolve);
-  });
-}
-
 /*
  * Run an xpcshell subprocess and crash it.
  *
  * @param setup
  *        A string of JavaScript code to execute in the subprocess
  *        before crashing. If this is a function and not a string,
  *        it will have .toSource() called on it, and turned into
  *        a call to itself. (for programmer convenience)
@@ -38,17 +32,17 @@ function sendCommandAsync(command) {
  *         - extrafile is an nsIFile of the extra file
  *
  * @param canReturnZero
  *       If true, the subprocess may return with a zero exit code.
  *       Certain types of crashes may not cause the process to
  *       exit with an error.
  *
  */
-async function do_crash(setup, callback, canReturnZero) {
+function do_crash(setup, callback, canReturnZero) {
   // get current process filename (xpcshell)
   let bin = Services.dirsvc.get("XREExeF", Ci.nsIFile);
   if (!bin.exists()) {
     // weird, can't find xpcshell binary?
     do_throw("Can't find xpcshell binary!");
   }
   // get Gre dir (GreD)
   let greD = Services.dirsvc.get("GreD", Ci.nsIFile);
@@ -87,17 +81,17 @@ async function do_crash(setup, callback,
     env.set("CRASHES_EVENTS_DIR", "");
   }
 
   if (!canReturnZero) {
     // should exit with an error (should have crashed)
     Assert.notEqual(process.exitValue, 0);
   }
 
-  await handleMinidump(callback);
+  handleMinidump(callback);
 }
 
 function getMinidump() {
   let en = do_get_tempdir().directoryEntries;
   while (en.hasMoreElements()) {
     let f = en.nextFile;
     if (f.leafName.substr(-4) == ".dmp") {
       return f;
@@ -125,68 +119,71 @@ function runMinidumpAnalyzer(dumpFile, a
   let args = [];
   if (additionalArgs) {
     args = args.concat(additionalArgs);
   }
   args.push(dumpFile.path);
   process.run(true /* blocking */, args, args.length);
 }
 
-async function handleMinidump(callback) {
+function handleMinidump(callback) {
   // find minidump
   let minidump = getMinidump();
 
   if (minidump == null) {
     do_throw("No minidump found!");
   }
 
   let extrafile = minidump.clone();
   extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra";
 
   let memoryfile = minidump.clone();
   memoryfile.leafName = memoryfile.leafName.slice(0, -4) + ".memory.json.gz";
 
-  let cleanup = function() {
-    [minidump, extrafile, memoryfile].forEach(file => {
-      if (file.exists()) {
-        file.remove(false);
-      }
-    });
-  };
-
   // Just in case, don't let these files linger.
-  registerCleanupFunction(cleanup);
+  registerCleanupFunction(function() {
+    if (minidump.exists()) {
+      minidump.remove(false);
+    }
+    if (extrafile.exists()) {
+      extrafile.remove(false);
+    }
+    if (memoryfile.exists()) {
+      memoryfile.remove(false);
+    }
+  });
 
   Assert.ok(extrafile.exists());
-  let data = await OS.File.read(extrafile.path);
-  let decoder = new TextDecoder();
-  let extra = JSON.parse(decoder.decode(data));
+  let extra = parseKeyValuePairsFromFile(extrafile);
 
   if (callback) {
-    await callback(minidump, extra, extrafile);
+    callback(minidump, extra, extrafile);
   }
 
-  cleanup();
-}
-
-function spinEventLoop() {
-  return new Promise(resolve => {
-    executeSoon(resolve);
-  });
+  if (minidump.exists()) {
+    minidump.remove(false);
+  }
+  if (extrafile.exists()) {
+    extrafile.remove(false);
+  }
+  if (memoryfile.exists()) {
+    memoryfile.remove(false);
+  }
 }
 
 /**
  * Helper for testing a content process crash.
  *
  * This variant accepts a setup function which runs in the content process
  * to set data as needed _before_ the crash.  The tail file triggers a generic
  * crash after setup.
  */
-async function do_content_crash(setup, callback) {
+function do_content_crash(setup, callback) {
   do_load_child_test_harness();
+  do_test_pending();
 
   // Setting the minidump path won't work in the child, so we need to do
   // that here.
   let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
     Ci.nsICrashReporter
   );
   crashReporter.minidumpPath = do_get_tempdir();
 
@@ -197,39 +194,49 @@ async function do_content_crash(setup, c
   let tailfile = do_get_file("../unit/crasher_subprocess_tail.js");
   if (setup) {
     if (typeof setup == "function") {
       // funky, but convenient
       setup = "(" + setup.toSource() + ")();";
     }
   }
 
+  let handleCrash = function() {
+    let id = getMinidump().leafName.slice(0, -4);
+    Services.crashmanager.ensureCrashIsPresent(id).then(() => {
+      try {
+        handleMinidump(callback);
+      } catch (x) {
+        do_report_unexpected_exception(x);
+      }
+      do_test_finished();
+    });
+  };
+
   do_get_profile();
-  await makeFakeAppDir();
-  await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");');
-  await sendCommandAsync(setup);
-  await sendCommandAsync('load("' + tailfile.path.replace(/\\/g, "/") + '");');
-  await spinEventLoop();
-  let id = getMinidump().leafName.slice(0, -4);
-  await Services.crashmanager.ensureCrashIsPresent(id);
-  try {
-    await handleMinidump(callback);
-  } catch (x) {
-    do_report_unexpected_exception(x);
-  }
+  makeFakeAppDir().then(() => {
+    sendCommand('load("' + headfile.path.replace(/\\/g, "/") + '");', () =>
+      sendCommand(setup, () =>
+        sendCommand('load("' + tailfile.path.replace(/\\/g, "/") + '");', () =>
+          executeSoon(handleCrash)
+        )
+      )
+    );
+  });
 }
 
 /**
  * Helper for testing a content process crash.
  *
  * This variant accepts a trigger function which runs in the content process
  * and does something to _trigger_ the crash.
  */
-async function do_triggered_content_crash(trigger, callback) {
+function do_triggered_content_crash(trigger, callback) {
   do_load_child_test_harness();
+  do_test_pending();
 
   // Setting the minidump path won't work in the child, so we need to do
   // that here.
   let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
     Ci.nsICrashReporter
   );
   crashReporter.minidumpPath = do_get_tempdir();
 
@@ -238,26 +245,38 @@ async function do_triggered_content_cras
   let headfile = do_get_file("../unit/crasher_subprocess_head.js");
   if (trigger) {
     if (typeof trigger == "function") {
       // funky, but convenient
       trigger = "(" + trigger.toSource() + ")();";
     }
   }
 
+  let handleCrash = function() {
+    let id = getMinidump().leafName.slice(0, -4);
+    Services.crashmanager.ensureCrashIsPresent(id).then(() => {
+      try {
+        handleMinidump(callback);
+      } catch (x) {
+        do_report_unexpected_exception(x);
+      }
+      do_test_finished();
+    });
+  };
+
   do_get_profile();
-  await makeFakeAppDir();
-  await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");');
-  await sendCommandAsync(trigger);
-  await spinEventLoop();
-  let id = getMinidump().leafName.slice(0, -4);
-  await Services.crashmanager.ensureCrashIsPresent(id);
-  try {
-    await handleMinidump(callback);
-  } catch (x) {
-    do_report_unexpected_exception(x);
-  }
+  makeFakeAppDir().then(() => {
+    sendCommand('load("' + headfile.path.replace(/\\/g, "/") + '");', () =>
+      sendCommand(trigger, () => executeSoon(handleCrash))
+    );
+  });
 }
 
 // Import binary APIs via js-ctypes.
 var { CrashTestUtils } = ChromeUtils.import(
   "resource://test/CrashTestUtils.jsm"
 );
+var {
+  parseKeyValuePairs,
+  parseKeyValuePairsFromFile,
+  parseKeyValuePairsFromFileAsync,
+  parseKeyValuePairsFromLines,
+} = ChromeUtils.import("resource://gre/modules/KeyValueParser.jsm");
--- a/toolkit/crashreporter/test/unit/head_win64cfi.js
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -185,30 +185,28 @@ function assertStack(stack, expected) {
 // Performs a crash, runs minidump-analyzer, and checks expected stack analysis.
 //
 // how: The crash to perform. Constants defined in both CrashTestUtils.jsm
 //   and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL)
 // expectedStack: An array of {"symbol", "trust"} where trust is "cfi",
 //   "context", "scan", et al. May be null if you don't need to check the stack.
 // minidumpAnalyzerArgs: An array of additional arguments to pass to
 //   minidump-analyzer.exe.
-async function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
+function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
   // Setup is run in the subprocess so we cannot use any closures.
   let setupFn = "crashType = CrashTestUtils." + how + ";";
 
-  let callbackFn = async function(minidumpFile, extra, extraFile) {
+  let callbackFn = function(minidumpFile, extra, extraFile) {
     runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
 
     // Refresh updated extra data
-    let data = await OS.File.read(extraFile.path);
-    let decoder = new TextDecoder();
-    extra = JSON.parse(decoder.decode(data));
+    extra = parseKeyValuePairsFromFile(extraFile);
 
     initTestCrasherSymbols();
-    let stackTraces = extra.StackTraces;
+    let stackTraces = JSON.parse(extra.StackTraces);
     let crashingThreadIndex = stackTraces.crash_info.crashing_thread;
     gModules = stackTraces.modules;
     let crashingFrames = stackTraces.threads[crashingThreadIndex].frames;
 
     dumpStackFrames(crashingFrames, 10);
 
     assertStack(crashingFrames, expectedStack);
   };
--- a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
+++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js
@@ -100,13 +100,13 @@ function after_osfile_crash_exn(mdump, e
   info("Keys: " + Object.keys(state).join(", "));
   Assert.equal(data.phase, "profile-before-change");
   Assert.ok(!state.shutdown);
   Assert.ok(state.worker);
   Assert.ok(!!state.latestSent);
   Assert.equal(state.latestSent[1], "read");
 }
 
-add_task(async function run_test() {
-  await do_crash(setup_crash, after_crash);
-  await do_crash(setup_osfile_crash_noerror, after_osfile_crash_noerror);
-  await do_crash(setup_osfile_crash_exn, after_osfile_crash_exn);
-});
+function run_test() {
+  do_crash(setup_crash, after_crash);
+  do_crash(setup_osfile_crash_noerror, after_osfile_crash_noerror);
+  do_crash(setup_osfile_crash_exn, after_osfile_crash_exn);
+}
--- a/toolkit/crashreporter/test/unit/test_crash_abort.js
+++ b/toolkit/crashreporter/test/unit/test_crash_abort.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-add_task(async function run_test() {
+function run_test() {
   // Try crashing with an abort().
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_ABORT;
       crashReporter.annotateCrashReport("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
@@ -1,23 +1,23 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "Yes");
       Cu.getJSTestingFunctions().reportLargeAllocationFailure();
       Cu.forceGC();
     },
     function(mdump, extra) {
       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
@@ -1,17 +1,17 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "Yes");
 
       function crashWhileReporting() {
         CrashTestUtils.crash(crashType);
       }
 
@@ -19,9 +19,9 @@ add_task(async function run_test() {
       Cu.getJSTestingFunctions().reportLargeAllocationFailure();
     },
     function(mdump, extra) {
       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
@@ -1,22 +1,22 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "Yes");
       Cu.getJSTestingFunctions().reportOutOfMemory();
       Cu.forceGC();
     },
     function(mdump, extra) {
       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
@@ -1,17 +1,17 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "Yes");
 
       // GC now to avoid having it happen randomly later, which would make the
       // test bogusly fail. See comment below.
       Cu.forceGC();
 
@@ -28,9 +28,9 @@ add_task(async function run_test() {
       // Theoretically, GC can happen any time, so it is just possible that
       // this property could be "Recovered" even if the implementation is
       // correct. More likely, though, that indicates a bug, so only accept
       // "Reported".
       Assert.equal(extra.JSOutOfMemory, "Reported");
     },
     true
   );
-});
+}
--- 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
@@ -1,17 +1,17 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "Yes");
       Cu.getJSTestingFunctions().reportOutOfMemory();
       Cu.forceGC(); // recover from first OOM
       Cu.getJSTestingFunctions().reportOutOfMemory();
     },
     function(mdump, extra) {
@@ -20,9 +20,9 @@ add_task(async function run_test() {
       // 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,17 +1,17 @@
-add_task(async function run_test() {
+function run_test() {
   // Try crashing with a runtime abort
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("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
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_oom.js
+++ b/toolkit/crashreporter/test/unit/test_crash_oom.js
@@ -1,21 +1,21 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
       crashReporter.annotateCrashReport("TestKey", "Yes");
     },
     function(mdump, extra) {
       Assert.equal(extra.TestKey, "Yes");
       Assert.ok("OOMAllocationSize" in extra);
       Assert.ok(Number(extra.OOMAllocationSize) > 0);
     },
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_phc.js
+++ b/toolkit/crashreporter/test/unit/test_crash_phc.js
@@ -7,38 +7,38 @@ function check(extra, size) {
   Assert.equal(extra.PHCUsableSize, size);
 
   // These are strings holding comma-separated lists of decimal addresses.
   // Sometimes on Mac they have a single entry.
   Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCAllocStack));
   Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCFreeStack));
 }
 
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_crash_phc.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
     },
     function(mdump, extra) {
       // CRASH_PHC_USE_AFTER_FREE uses 32 for the size.
       check(extra, 32);
     },
     true
   );
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
     },
     function(mdump, extra) {
       // CRASH_PHC_DOUBLE_FREE uses 64 for the size.
       check(extra, 64);
     },
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_purevirtual.js
+++ b/toolkit/crashreporter/test/unit/test_crash_purevirtual.js
@@ -1,29 +1,29 @@
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_crash_purevirtual.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
   var isOSX = "nsILocalFileMac" in Ci;
   if (isOSX) {
     dump(
       "INFO | test_crash_purevirtual.js | TODO: purecalls not caught on OS X\n"
     );
     return;
   }
 
   // Try crashing with a pure virtual call
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_PURE_VIRTUAL_CALL;
       crashReporter.annotateCrashReport("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_rust_panic.js
+++ b/toolkit/crashreporter/test/unit/test_crash_rust_panic.js
@@ -1,15 +1,15 @@
-add_task(async function run_test() {
+function run_test() {
   // Try crashing with a Rust panic
-  await do_crash(
+  do_crash(
     function() {
       Cc["@mozilla.org/xpcom/debug;1"]
         .getService(Ci.nsIDebug2)
         .rustPanic("OH NO");
     },
     function(mdump, extra) {
       Assert.equal(extra.MozCrashReason, "OH NO");
     },
     // process will exit with a zero exit status
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js
+++ b/toolkit/crashreporter/test/unit/test_crash_rust_panic_multiline.js
@@ -1,15 +1,15 @@
-add_task(async function run_test() {
+function run_test() {
   // Try crashing with a Rust panic
-  await do_crash(
+  do_crash(
     function() {
       Cc["@mozilla.org/xpcom/debug;1"]
         .getService(Ci.nsIDebug2)
         .rustPanic("OH NO\nOH NOES!");
     },
     function(mdump, extra) {
       Assert.equal(extra.MozCrashReason, "OH NO\nOH NOES!");
     },
     // process will exit with a zero exit status
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_terminator.js
+++ b/toolkit/crashreporter/test/unit/test_crash_terminator.js
@@ -30,11 +30,11 @@ function setup_crash() {
   Services.tm.spinEventLoopUntil(() => false);
 }
 
 function after_crash(mdump, extra) {
   info("Crash signature: " + JSON.stringify(extra, null, "\t"));
   Assert.equal(extra.ShutdownProgress, "profile-before-change");
 }
 
-add_task(async function run_test() {
-  await do_crash(setup_crash, after_crash);
-});
+function run_test() {
+  do_crash(setup_crash, after_crash);
+}
--- a/toolkit/crashreporter/test/unit/test_crash_thread_annotation.js
+++ b/toolkit/crashreporter/test/unit/test_crash_thread_annotation.js
@@ -1,18 +1,18 @@
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_crash_thread_annotation.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_INVALID_POINTER_DEREF;
     },
     function(mdump, extra) {
       Assert.ok("ThreadIdNameMapping" in extra);
     },
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
+++ b/toolkit/crashreporter/test/unit/test_crash_uncaught_exception.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-add_task(async function run_test() {
+function run_test() {
   // Try crashing with an uncaught exception.
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_UNCAUGHT_EXCEPTION;
       crashReporter.annotateCrashReport("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_win64cfi_alloc_large.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
     { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
     { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_EPILOG", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_EPILOG", [
     { symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
@@ -1,22 +1,22 @@
-add_task(async function run_test() {
+function run_test() {
   // Test that minidump-analyzer gracefully handles chained
   // unwind code entries that form a circular reference
   // (infinite loop).
   let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe");
   ok(exe);
 
   // Perform a crash. The PE used for unwind info should fail, resulting in
   // fallback behavior, calculating the first frame from thread context.
   // Further frames would be calculated with either frame_pointer or scan trust,
   // but should not be calculated via CFI. If we see CFI here that would be an
   // indication that either our alternative EXE was not used, or we failed to
   // abandon unwind info parsing.
-  await do_x64CFITest(
+  do_x64CFITest(
     "CRASH_X64CFI_ALLOC_SMALL",
     [
       { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
       { symbol: null, trust: "!cfi" },
     ],
     ["--force-use-module", exe.path]
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
@@ -1,22 +1,22 @@
-add_task(async function run_test() {
+function run_test() {
   // Test that minidump-analyzer gracefully handles chained
   // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference
   // (infinite loop).
   let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe");
   ok(exe);
 
   // Perform a crash. The PE used for unwind info should fail, resulting in
   // fallback behavior, calculating the first frame from thread context.
   // Further frames would be calculated with either frame_pointer or scan trust,
   // but should not be calculated via CFI. If we see CFI here that would be an
   // indication that either our alternative EXE was not used, or we failed to
   // abandon unwind info parsing.
-  await do_x64CFITest(
+  do_x64CFITest(
     "CRASH_X64CFI_ALLOC_SMALL",
     [
       { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
       { symbol: null, trust: "!cfi" },
     ],
     ["--force-use-module", exe.path]
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
@@ -1,21 +1,21 @@
-add_task(async function run_test() {
+function run_test() {
   // Test that minidump-analyzer gracefully handles an invalid pointer to the
   // exception unwind information.
   let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe");
   ok(exe);
 
   // Perform a crash. The PE used for unwind info should fail, resulting in
   // fallback behavior, calculating the first frame from thread context.
   // Further frames would be calculated with either frame_pointer or scan trust,
   // but should not be calculated via CFI. If we see CFI here that would be an
   // indication that either our alternative EXE was not used, or we failed to
   // abandon unwind info parsing.
-  await do_x64CFITest(
+  do_x64CFITest(
     "CRASH_X64CFI_ALLOC_SMALL",
     [
       { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
       { symbol: null, trust: "!cfi" },
     ],
     ["--force-use-module", exe.path]
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
@@ -1,20 +1,20 @@
-add_task(async function run_test() {
+function run_test() {
   // Test that minidump-analyzer gracefully handles corrupt PE files.
   let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe");
   ok(exe);
 
   // Perform a crash. The PE used for unwind info should fail, resulting in
   // fallback behavior, calculating the first frame from thread context.
   // Further frames would be calculated with either frame_pointer or scan trust,
   // but should not be calculated via CFI. If we see CFI here that would be an
   // indication that either our alternative EXE was not used, or we failed to
   // abandon unwind info parsing.
-  await do_x64CFITest(
+  do_x64CFITest(
     "CRASH_X64CFI_ALLOC_SMALL",
     [
       { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
       { symbol: null, trust: "!cfi" },
     ],
     ["--force-use-module", exe.path]
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
     { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
     { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
     { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
     { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
@@ -1,6 +1,6 @@
-add_task(async function run_test() {
-  await do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
     { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" },
     { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
@@ -1,12 +1,12 @@
-add_task(async function run_test() {
+function run_test() {
   // In the case of an unknown unwind code or missing CFI,
   // make certain we can still walk the stack via stack scan. The crashing
   // function places NO_MANS_LAND on the stack so it will get picked up via
   // stack scan.
-  await do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
+  do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
     { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" },
     // Trust may either be scan or frame_pointer; we don't really care as
     // long as the address is expected.
     { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null },
   ]);
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js
+++ b/toolkit/crashreporter/test/unit/test_crash_with_memory_report.js
@@ -1,21 +1,21 @@
-add_task(async function run_test() {
+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;
   }
 
   // This was shamelessly copied and stripped down from do_get_profile() in
   // head.js so that nsICrashReporter::saveMemoryReport can use a profile
   // within the crasher subprocess.
 
-  await do_crash(
+  do_crash(
     function() {
       // Delay crashing so that the memory report has time to complete.
       shouldDelay = true;
 
       let env = Cc["@mozilla.org/process/environment;1"].getService(
         Ci.nsIEnvironment
       );
       let profd = env.get("XPCSHELL_TEST_PROFILE_DIR");
@@ -44,9 +44,9 @@ add_task(async function run_test() {
 
       crashReporter.saveMemoryReport();
     },
     function(mdump, extra) {
       Assert.equal(extra.ContainsMemoryReport, "1");
     },
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter.js
@@ -54,22 +54,36 @@ function run_test() {
   try {
     cr.annotateCrashReport("foobar", "");
     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("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");
   // Replace previous data.
   cr.annotateCrashReport("TestKey", "testData2");
-  // Allow nul chars in annotations.
-  cr.annotateCrashReport("TestKey", "da\0ta");
 
+  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(
--- a/toolkit/crashreporter/test/unit/test_crashreporter_appmem.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_appmem.js
@@ -1,13 +1,13 @@
-add_task(async function run_test() {
-  await do_crash(
+function run_test() {
+  do_crash(
     function() {
       let appAddr = CrashTestUtils.saveAppMemory();
       crashReporter.registerAppMemory(appAddr, 32);
     },
     function(mdump, extra) {
       Assert.ok(mdump.exists());
       Assert.ok(mdump.fileSize > 0);
       Assert.ok(CrashTestUtils.dumpCheckMemory(mdump.path));
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
@@ -1,9 +1,9 @@
-add_task(async function run_test() {
+function run_test() {
   var is_win7_or_newer = false;
   var is_windows = false;
   var ph = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
     Ci.nsIHttpProtocolHandler
   );
   var match = ph.userAgent.match(/Windows NT (\d+).(\d+)/);
   if (match) {
     is_windows = true;
@@ -12,17 +12,17 @@ add_task(async function run_test() {
     match &&
     (parseInt(match[1]) > 6 ||
       (parseInt(match[1]) == 6 && parseInt(match[2]) >= 1))
   ) {
     is_win7_or_newer = true;
   }
 
   // try a basic crash
-  await do_crash(null, function(mdump, extra) {
+  do_crash(null, function(mdump, extra) {
     Assert.ok(mdump.exists());
     Assert.ok(mdump.fileSize > 0);
     Assert.ok("StartupTime" in extra);
     Assert.ok("CrashTime" in extra);
     Assert.ok(
       CrashTestUtils.dumpHasStream(
         mdump.path,
         CrashTestUtils.MD_THREAD_LIST_STREAM
@@ -46,39 +46,36 @@ add_task(async function run_test() {
           mdump.path,
           CrashTestUtils.MD_MEMORY_INFO_LIST_STREAM
         )
       );
     }
   });
 
   // check setting some basic data
-  await do_crash(
+  do_crash(
     function() {
       // Add various annotations
       crashReporter.annotateCrashReport("TestKey", "TestValue");
-      crashReporter.annotateCrashReport(
-        "TestUnicode",
-        "\u{1F4A9}\n\u{0000}Escape"
-      );
+      crashReporter.annotateCrashReport("TestUnicode", "\u{1F4A9}");
       crashReporter.annotateCrashReport("Add-ons", "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.TestUnicode, "\u{1F4A9}\n\u{0000}Escape");
+      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(
@@ -91,17 +88,17 @@ add_task(async function run_test() {
       );
       Assert.ok(
         !("TelemetryServerURL" in extra),
         "The TelemetryServerURL field is omitted by default"
       );
     }
   );
 
-  await do_crash(
+  do_crash(
     function() {
       // Enable the FHR, official policy bypass (since we're in a test) and
       // specify a telemetry server & client ID.
       Services.prefs.setBoolPref(
         "datareporting.policy.dataSubmissionPolicyBypassNotification",
         true
       );
       Services.prefs.setBoolPref(
@@ -144,17 +141,17 @@ add_task(async function run_test() {
       Assert.equal(
         extra.TelemetryServerURL,
         "http://a.telemetry.server",
         "The TelemetryServerURL matches the expected value"
       );
     }
   );
 
-  await do_crash(
+  do_crash(
     function() {
       // Disable the FHR upload, no telemetry annotations should be present.
       Services.prefs.setBoolPref(
         "datareporting.policy.dataSubmissionPolicyBypassNotification",
         true
       );
       Services.prefs.setBoolPref(
         "datareporting.healthreport.uploadEnabled",
@@ -178,17 +175,17 @@ add_task(async function run_test() {
       );
       Assert.ok(
         !("TelemetryServerURL" in extra),
         "The TelemetryServerURL field is omitted when FHR upload is disabled"
       );
     }
   );
 
-  await do_crash(
+  do_crash(
     function() {
       // No telemetry annotations should be present if the user has not been
       // notified yet
       Services.prefs.setBoolPref(
         "datareporting.policy.dataSubmissionPolicyBypassNotification",
         false
       );
       Services.prefs.setBoolPref(
@@ -212,9 +209,9 @@ add_task(async function run_test() {
         "The TelemetryClientId field is omitted when FHR upload is disabled"
       );
       Assert.ok(
         !("TelemetryServerURL" in extra),
         "The TelemetryServerURL field is omitted when FHR upload is disabled"
       );
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
+++ b/toolkit/crashreporter/test/unit/test_oom_annotation_windows.js
@@ -1,17 +1,17 @@
-add_task(async function run_test() {
+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;
   }
 
-  await do_crash(
+  do_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
       crashReporter.annotateCrashReport("TestKey", "Yes");
     },
     function(mdump, extra) {
       Assert.equal(extra.TestKey, "Yes");
       Assert.ok("OOMAllocationSize" in extra);
       Assert.ok(Number(extra.OOMAllocationSize) > 0);
@@ -20,9 +20,9 @@ add_task(async function run_test() {
       Assert.ok("AvailableVirtualMemory" in extra);
       Assert.ok("TotalPageFile" in extra);
       Assert.ok("AvailablePageFile" in extra);
       Assert.ok("TotalPhysicalMemory" in extra);
       Assert.ok("AvailablePhysicalMemory" in extra);
     },
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit/test_override_exception_handler.js
+++ b/toolkit/crashreporter/test/unit/test_override_exception_handler.js
@@ -1,11 +1,11 @@
-add_task(async function run_test() {
+function run_test() {
   // Ensure that attempting to override the exception handler doesn't cause
   // us to lose our exception handler.
-  await do_crash(
+  do_crash(
     function() {
       CrashTestUtils.TryOverrideExceptionHandler();
     },
     function(mdump, extra) {},
     true
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_annotation.js
@@ -1,32 +1,32 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+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;
   }
 
   // TelemetrySession setup will trigger the session annotation
   let scope = {};
   ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", scope);
   scope.TelemetryController.testSetup();
 
   // Try crashing with a runtime abort
-  await do_content_crash(
+  do_content_crash(
     function() {
       crashType = CrashTestUtils.CRASH_MOZ_CRASH;
       crashReporter.annotateCrashReport("TestKey", "TestValue");
       crashReporter.appendAppNotesToCrashReport("!!!foo!!!");
     },
     function(mdump, extra) {
       Assert.equal(extra.TestKey, "TestValue");
       Assert.ok("ProcessType" in extra);
       Assert.ok("StartupTime" in extra);
       Assert.ok("TelemetrySessionId" in extra);
       Assert.notEqual(extra.Notes.indexOf("!!!foo!!!"), -1);
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_exception_time_annotation.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_exception_time_annotation.js
@@ -1,21 +1,21 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+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 an OOM
-  await do_content_crash(
+  do_content_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
     },
     function(mdump, extra) {
       Assert.ok("OOMAllocationSize" in extra);
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_memory_list.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_memory_list.js
@@ -1,33 +1,33 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+function run_test() {
   var is_win7_or_newer = false;
   var ph = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
     Ci.nsIHttpProtocolHandler
   );
   var match = ph.userAgent.match(/Windows NT (\d+).(\d+)/);
   if (
     match &&
     (parseInt(match[1]) > 6 ||
       (parseInt(match[1]) == 6 && parseInt(match[2]) >= 1))
   ) {
     is_win7_or_newer = true;
   }
 
-  await do_content_crash(null, function(mdump, extra) {
+  do_content_crash(null, function(mdump, extra) {
     Assert.ok(mdump.exists());
     Assert.ok(mdump.fileSize > 0);
     if (is_win7_or_newer) {
       Assert.ok(
         CrashTestUtils.dumpHasStream(
           mdump.path,
           CrashTestUtils.MD_MEMORY_INFO_LIST_STREAM
         )
       );
     }
   });
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_oom_annotation_windows.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_oom_annotation_windows.js
@@ -1,27 +1,27 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+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 an OOM
-  await do_content_crash(
+  do_content_crash(
     function() {
       crashType = CrashTestUtils.CRASH_OOM;
     },
     function(mdump, extra) {
       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);
       Assert.ok("AvailablePhysicalMemory" in extra);
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_phc.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_phc.js
@@ -1,20 +1,20 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
-  await do_content_crash(
+  do_content_crash(
     function() {
       crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
     },
     function(mdump, extra) {
       Assert.equal(extra.PHCKind, "FreedPage");
 
       // This is a string holding a decimal address.
       Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
@@ -23,9 +23,9 @@ add_task(async function run_test() {
       Assert.equal(extra.PHCUsableSize, 32);
 
       // These are strings holding comma-separated lists of decimal addresses.
       // Sometimes on Mac they have a single entry.
       Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCAllocStack));
       Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCFreeStack));
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_phc2.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_phc2.js
@@ -1,23 +1,23 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
   // For some unknown reason, having two do_content_crash() calls in a single
   // test doesn't work. That explains why this test exists separately from
   // test_content_phc.js.
-  await do_content_crash(
+  do_content_crash(
     function() {
       crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
     },
     function(mdump, extra) {
       Assert.equal(extra.PHCKind, "FreedPage");
 
       // This is a string holding a decimal address.
       Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
@@ -26,9 +26,9 @@ add_task(async function run_test() {
       Assert.equal(extra.PHCUsableSize, 64);
 
       // These are strings holding comma-separated lists of decimal addresses.
       // Sometimes on Mac they have a single entry.
       Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCAllocStack));
       Assert.ok(/^(\d+,)*\d+$/.test(extra.PHCFreeStack));
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_rust_panic.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_rust_panic.js
@@ -1,23 +1,23 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_content_rust_panic.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
   // Try crashing with a Rust panic
-  await do_triggered_content_crash(
+  do_triggered_content_crash(
     function() {
       Cc["@mozilla.org/xpcom/debug;1"]
         .getService(Ci.nsIDebug2)
         .rustPanic("OH NO");
     },
     function(mdump, extra) {
       Assert.equal(extra.MozCrashReason, "OH NO");
     }
   );
-});
+}
--- a/toolkit/crashreporter/test/unit_ipc/test_content_rust_panic_multiline.js
+++ b/toolkit/crashreporter/test/unit_ipc/test_content_rust_panic_multiline.js
@@ -1,23 +1,23 @@
 /* import-globals-from ../unit/head_crashreporter.js */
 load("../unit/head_crashreporter.js");
 
-add_task(async function run_test() {
+function run_test() {
   if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
     dump(
       "INFO | test_content_rust_panic.js | Can't test crashreporter in a non-libxul build.\n"
     );
     return;
   }
 
   // Try crashing with a Rust panic
-  await do_triggered_content_crash(
+  do_triggered_content_crash(
     function() {
       Cc["@mozilla.org/xpcom/debug;1"]
         .getService(Ci.nsIDebug2)
         .rustPanic("OH NO\nOH NOES!");
     },
     function(mdump, extra) {
       Assert.equal(extra.MozCrashReason, "OH NO\nOH NOES!");
     }
   );
-});
+}
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -103,16 +103,17 @@
   "httpd.js": ["HTTP_400", "HTTP_401", "HTTP_402", "HTTP_403", "HTTP_404", "HTTP_405", "HTTP_406", "HTTP_407", "HTTP_408", "HTTP_409", "HTTP_410", "HTTP_411", "HTTP_412", "HTTP_413", "HTTP_414", "HTTP_415", "HTTP_417", "HTTP_500", "HTTP_501", "HTTP_502", "HTTP_503", "HTTP_504", "HTTP_505", "HttpError", "HttpServer"],
   "import_module.jsm": ["MODULE_IMPORTED", "MODULE_URI", "SUBMODULE_IMPORTED", "same_scope", "SUBMODULE_IMPORTED_TO_SCOPE"],
   "import_sub_module.jsm": ["SUBMODULE_IMPORTED", "test_obj"],
   "InlineSpellChecker.jsm": ["InlineSpellChecker", "SpellCheckHelper"],
   "jsdebugger.jsm": ["addDebuggerToGlobal", "addSandboxedDebuggerToGlobal"],
   "jsesc.js": ["jsesc"],
   "json2.js": ["JSON"],
   "keys.js": ["BulkKeyBundle", "SyncKeyBundle"],
+  "KeyValueParser.jsm": ["parseKeyValuePairsFromLines", "parseKeyValuePairs", "parseKeyValuePairsFromFile", "parseKeyValuePairsFromFileAsync"],
   "kinto-http-client.js": ["KintoHttpClient"],
   "kinto-offline-client.js": ["Kinto"],
   "kinto-storage-adapter.js": ["FirefoxAdapter"],
   "kvstore.jsm": ["KeyValueService"],
   "L10nRegistry.jsm": ["L10nRegistry", "FileSource", "IndexedFileSource"],
   "loader-plugin-raw.jsm": ["requireRawId"],
   "loader.js": ["WorkerDebuggerLoader", "worker"],
   "Loader.jsm": ["DevToolsLoader", "require", "loader"],