Bug 1110887 - Make the plugin crash reporter work with e10s. r=felipe.
authorMike Conley <mconley@mozilla.com>
Fri, 27 Mar 2015 23:34:15 -0400
changeset 257364 91df06a7b64384be815a2703da117b4dc59b62af
parent 257363 c99243d545e26e3bb05ecac0b7a40cd10ff78453
child 257365 d3fb698e4f939041190ccf125bfc6f5ecf3bda04
push id8007
push userraliiev@mozilla.com
push dateMon, 11 May 2015 19:23:16 +0000
treeherdermozilla-aurora@e2ce1aac996e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs1110887
milestone40.0a1
Bug 1110887 - Make the plugin crash reporter work with e10s. r=felipe. This patch does the following: * Has the gPluginHandler tell content processes when a NPAPI plugin crashes * Introduces PluginCrashReporter, and renames TabCrashReporter.jsm to ContentCrashReporters.jsm. * Makes gPluginHandler use PluginCrashReporter to submit plugin crashes. * If a plugin crash report is submitted, puts all visible instances into the submitted state. * Makes the plugin crashed notification bar work with run IDs. * Removes event and message listeners in PluginContent when uninitting.
browser/base/content/browser-plugins.js
browser/base/content/browser.js
browser/components/nsBrowserGlue.js
browser/modules/ContentCrashReporters.jsm
browser/modules/PluginContent.jsm
browser/modules/TabCrashReporter.jsm
browser/modules/moz.build
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -8,17 +8,18 @@ var gPluginHandler = {
   PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
   MESSAGES: [
     "PluginContent:ShowClickToPlayNotification",
     "PluginContent:RemoveNotification",
     "PluginContent:UpdateHiddenPluginUI",
     "PluginContent:HideNotificationBar",
     "PluginContent:ShowInstallNotification",
     "PluginContent:InstallSinglePlugin",
-    "PluginContent:ShowPluginCrashedNotification",
+    "PluginContent:ShowNPAPIPluginCrashedNotification",
+    "PluginContent:ShowGMPCrashedNotification",
     "PluginContent:SubmitReport",
     "PluginContent:LinkClickCallback",
   ],
 
   init: function () {
     const mm = window.messageManager;
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
@@ -56,22 +57,30 @@ var gPluginHandler = {
       case "PluginContent:HideNotificationBar":
         this.hideNotificationBar(msg.target, msg.data.name);
         break;
       case "PluginContent:ShowInstallNotification":
         return this.showInstallNotification(msg.target, msg.data.pluginInfo);
       case "PluginContent:InstallSinglePlugin":
         this.installSinglePlugin(msg.data.pluginInfo);
         break;
-      case "PluginContent:ShowPluginCrashedNotification":
-        this.showPluginCrashedNotification(msg.target, msg.data.messageString,
-                                           msg.data.pluginDumpID, msg.data.browserDumpID);
+      case "PluginContent:ShowNPAPIPluginCrashedNotification":
+        this.showNPAPIPluginCrashedNotification(msg.target, msg.data.message,
+                                                msg.data.runID);
+        break;
+      case "PluginContent:ShowGMPCrashedNotification":
+        this.showGMPCrashedNotification(msg.target,
+                                        msg.data.messageString,
+                                        msg.data.pluginDumpID,
+                                        msg.data.browserDumpID);
         break;
       case "PluginContent:SubmitReport":
-        this.submitReport(msg.data.pluginDumpID, msg.data.browserDumpID, msg.data.keyVals);
+        if (AppConstants.MOZ_CRASHREPORTER) {
+          this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn);
+        }
         break;
       case "PluginContent:LinkClickCallback":
         switch (msg.data.name) {
           case "managePlugins":
           case "openHelpPage":
           case "openPluginUpdatePage":
             this[msg.data.name].apply(this);
             break;
@@ -97,25 +106,23 @@ var gPluginHandler = {
   },
 
   // Callback for user clicking on the link in a click-to-play plugin
   // (where the plugin has an update)
   openPluginUpdatePage: function () {
     openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab");
   },
 
-#ifdef MOZ_CRASHREPORTER
-  submitReport: function submitReport(pluginDumpID, browserDumpID, keyVals) {
-    keyVals = keyVals || {};
-    this.CrashSubmit.submit(pluginDumpID, { recordSubmission: true,
-                                            extraExtraKeyVals: keyVals });
-    if (browserDumpID)
-      this.CrashSubmit.submit(browserDumpID);
+  submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
+    if (!AppConstants.MOZ_CRASHREPORTER) {
+      return;
+    }
+    Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", submitURLOptIn);
+    PluginCrashReporter.submitCrashReport(runID, keyVals);
   },
-#endif
 
   // Callback for user clicking a "reload page" link
   reloadPage: function (browser) {
     browser.reload();
   },
 
   // Callback for user clicking the help icon
   openHelpPage: function () {
@@ -450,38 +457,96 @@ var gPluginHandler = {
 
   contextMenuCommand: function (browser, plugin, command) {
     browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand",
       { command: command }, { plugin: plugin });
   },
 
   // Crashed-plugin observer. Notified once per plugin crash, before events
   // are dispatched to individual plugin instances.
-  pluginCrashed : function(subject, topic, data) {
+  NPAPIPluginCrashed : function(subject, topic, data) {
     let propertyBag = subject;
     if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
-        !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
-     return;
+        !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+        !propertyBag.hasKey("runID") ||
+        !propertyBag.hasKey("pluginName")) {
+      Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
+                     "cannot be read.");
+      return;
+    }
 
-#ifdef MOZ_CRASHREPORTER
+    let runID = propertyBag.getPropertyAsUint32("runID");
+    let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
+    let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
     let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
-    let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
-    let shouldSubmit = gCrashReporter.submitReports;
-    let doPrompt     = true; // XXX followup to get via gCrashReporter
 
-    // Submit automatically when appropriate.
-    if (pluginDumpID && shouldSubmit && !doPrompt) {
-      this.submitReport(pluginDumpID, browserDumpID);
-      // Submission is async, so we can't easily show failure UI.
-      propertyBag.setPropertyAsBool("submittedCrashReport", true);
+    // If we don't have a minidumpID, we can't (or didn't) submit anything.
+    // This can happen if the plugin is killed from the task manager.
+    let state;
+    if (!AppConstants.MOZ_CRASHREPORTER || !gCrashReporter.enabled) {
+      // This state tells the user that crash reporting is disabled, so we
+      // cannot send a report.
+      state = "noSubmit";
+    } else if (!pluginDumpID) {
+      // This state tells the user that there is no crash report available.
+      state = "noReport";
+    } else {
+      // This state asks the user to submit a crash report.
+      state = "please";
     }
-#endif
+
+    let mm = window.getGroupMessageManager("browsers");
+    mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
+                             { pluginName, runID, state });
   },
 
-  showPluginCrashedNotification: function (browser, messageString, pluginDumpID, browserDumpID) {
+  showNPAPIPluginCrashedNotification: function (browser, messageString, runID) {
+    let crashReportCallback;
+
+    if (AppConstants.MOZ_CRASHREPORTER &&
+        PluginCrashReporter.hasCrashReport(runID)) {
+      crashReportCallback = () => {
+        PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
+      };
+    }
+
+    this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
+  },
+
+  /**
+   * For now, GMP crashes are handled separately from NPAPI plugin crashes,
+   * since the latter are not yet working for e10s. These will be unified
+   * once e10s support is added for GMP crash reporting in bug 1146955.
+   */
+  showGMPCrashedNotification: function (browser, messageString,
+                                        pluginDumpID, browserDumpID) {
+    let crashReportCallback;
+
+    if (AppConstants.MOZ_CRASHREPORTER && pluginDumpID) {
+      crashReportCallback = () => {
+        PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
+      };
+    }
+
+    this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
+  },
+
+  /**
+   * A helper function for showing the plugin crashed notification bar.
+   *
+   * @param browser
+   *        The browser that contains the crashing plugin.
+   * @param messageString
+   *        The message to display in the notification.
+   * @param crashReportCallback
+   *        Optional. Pass a function to submit a crash report for this plugin
+   *        crash if a report exists. If no function is passed, the Submit Report
+   *        button will not be added.
+   */
+  _showPluginCrashedNotification: function (browser, messageString, crashReportCallback) {
     // If there's already an existing notification bar, don't do anything.
     let notificationBox = gBrowser.getNotificationBox(browser);
     let notification = notificationBox.getNotificationWithValue("plugin-crashed");
     if (notification)
       return;
 
     // Configure the notification bar
     let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
@@ -493,26 +558,26 @@ var gPluginHandler = {
 
     let buttons = [{
       label: reloadLabel,
       accessKey: reloadKey,
       popup: null,
       callback: function() { browser.reload(); },
     }];
 
-#ifdef MOZ_CRASHREPORTER
-    let submitButton = {
-      label: submitLabel,
-      accessKey: submitKey,
-      popup: null,
-        callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
-    };
-    if (pluginDumpID)
+    if (AppConstants.MOZ_CRASHREPORTER && crashReportCallback) {
+      let submitButton = {
+        label: submitLabel,
+        accessKey: submitKey,
+        popup: null,
+        callback: crashReportCallback,
+      };
+
       buttons.push(submitButton);
-#endif
+    }
 
     notification = notificationBox.appendNotification(messageString, "plugin-crashed",
                                                       iconURL, priority, buttons);
 
     // Add the "learn more" link.
     let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let link = notification.ownerDocument.createElementNS(XULNS, "label");
     link.className = "text-link";
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -34,16 +34,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
                                    "@mozilla.org/browser/favicon-service;1",
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
@@ -190,17 +192,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
   "resource://gre/modules/FxAccounts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gWebRTCUI",
   "resource:///modules/webrtcUI.jsm", "webrtcUI");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
-  "resource:///modules/TabCrashReporter.jsm");
+  "resource:///modules/ContentCrashReporters.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
+  "resource:///modules/ContentCrashReporters.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
   "resource:///modules/FormValidationHandler.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
   "resource:///modules/UITour.jsm");
 
@@ -920,17 +924,17 @@ function RedirectLoad({ target: browser,
 }
 
 var gBrowserInit = {
   delayedStartupFinished: false,
 
   onLoad: function() {
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
-    Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
+    Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed", false);
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
     // These routines add message listeners. They must run before
     // loading the frame script to ensure that we don't miss any
     // message sent between when the frame script is loaded and when
     // the listener is registered.
     DOMLinkHandler.init();
@@ -1238,21 +1242,16 @@ var gBrowserInit = {
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
     LightweightThemeListener.init();
 
-#ifdef MOZ_CRASHREPORTER
-    if (gMultiProcessBrowser)
-      TabCrashReporter.init();
-#endif
-
     Services.telemetry.getHistogramById("E10S_WINDOW").add(gMultiProcessBrowser);
 
     SidebarUI.startDelayedLoad();
 
     UpdateUrlbarSearchSplitterState();
 
     if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
         !focusAndSelectUrlBar()) {
@@ -1485,17 +1484,17 @@ var gBrowserInit = {
     gHistorySwipeAnimation.uninit();
 
     FullScreen.uninit();
 
 #ifdef MOZ_SERVICES_SYNC
     gFxAccounts.uninit();
 #endif
 
-    Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+    Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
     } catch (ex) {
     }
 
     PlacesToolbarHelper.uninit();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -117,16 +117,23 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 
 #ifdef E10S_TESTING_ONLY
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
                                   "resource://gre/modules/UpdateChannel.jsm");
 #endif
 
+#ifdef MOZ_CRASHREPORTER
+XPCOMUtils.defineLazyModuleGetter(this, "TabCrashReporter",
+                                  "resource:///modules/ContentCrashReporters.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
+                                  "resource:///modules/ContentCrashReporters.jsm");
+#endif
+
 XPCOMUtils.defineLazyGetter(this, "ShellService", function() {
   try {
     return Cc["@mozilla.org/browser/shell-service;1"].
            getService(Ci.nsIShellService);
   }
   catch(ex) {
     return null;
   }
@@ -728,16 +735,21 @@ BrowserGlue.prototype = {
       id: "firefox-devedition@mozilla.org",
       name: themeName,
       headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
       iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
       author: vendorShortName,
     });
 #endif
 
+#ifdef MOZ_CRASHREPORTER
+    TabCrashReporter.init();
+    PluginCrashReporter.init();
+#endif
+
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
     AddonWatcher.init(this._notifySlowAddon);
   },
 
   _checkForOldBuildUpdates: function () {
     // check for update if our build is old
     if (Services.prefs.getBoolPref("app.update.enabled") &&
new file mode 100644
--- /dev/null
+++ b/browser/modules/ContentCrashReporters.jsm
@@ -0,0 +1,207 @@
+/* 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";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "TabCrashReporter", "PluginCrashReporter" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
+  "resource://gre/modules/CrashSubmit.jsm");
+
+this.TabCrashReporter = {
+  init: function () {
+    if (this.initialized)
+      return;
+    this.initialized = true;
+
+    Services.obs.addObserver(this, "ipc:content-shutdown", false);
+    Services.obs.addObserver(this, "oop-frameloader-crashed", false);
+
+    this.childMap = new Map();
+    this.browserMap = new WeakMap();
+  },
+
+  observe: function (aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "ipc:content-shutdown":
+        aSubject.QueryInterface(Ci.nsIPropertyBag2);
+
+        if (!aSubject.get("abnormal"))
+          return;
+
+        this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
+        break;
+
+      case "oop-frameloader-crashed":
+        aSubject.QueryInterface(Ci.nsIFrameLoader);
+
+        let browser = aSubject.ownerElement;
+        if (!browser)
+          return;
+
+        this.browserMap.set(browser, aSubject.childID);
+        break;
+    }
+  },
+
+  submitCrashReport: function (aBrowser) {
+    let childID = this.browserMap.get(aBrowser);
+    let dumpID = this.childMap.get(childID);
+    if (!dumpID)
+      return
+
+    if (CrashSubmit.submit(dumpID, { recordSubmission: true })) {
+      this.childMap.set(childID, null); // Avoid resubmission.
+      this.removeSubmitCheckboxesForSameCrash(childID);
+    }
+  },
+
+  removeSubmitCheckboxesForSameCrash: function(childID) {
+    let enumerator = Services.wm.getEnumerator("navigator:browser");
+    while (enumerator.hasMoreElements()) {
+      let window = enumerator.getNext();
+      if (!window.gMultiProcessBrowser)
+        continue;
+
+      for (let browser of window.gBrowser.browsers) {
+        if (browser.isRemoteBrowser)
+          continue;
+
+        let doc = browser.contentDocument;
+        if (!doc.documentURI.startsWith("about:tabcrashed"))
+          continue;
+
+        if (this.browserMap.get(browser) == childID) {
+          this.browserMap.delete(browser);
+          browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
+          browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
+        }
+      }
+    }
+  },
+
+  onAboutTabCrashedLoad: function (aBrowser) {
+    if (!this.childMap)
+      return;
+
+    let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
+    if (!dumpID)
+      return;
+
+    aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
+  }
+}
+
+this.PluginCrashReporter = {
+  /**
+   * Makes the PluginCrashReporter ready to hear about and
+   * submit crash reports.
+   */
+  init() {
+    if (this.initialized) {
+      return;
+    }
+
+    this.initialized = true;
+    this.crashReports = new Map();
+
+    Services.obs.addObserver(this, "plugin-crashed", false);
+  },
+
+  observe(subject, topic, data) {
+    if (topic != "plugin-crashed") {
+      return;
+    }
+
+    let propertyBag = subject;
+    if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+        !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+        !propertyBag.hasKey("runID") ||
+        !propertyBag.hasKey("pluginName")) {
+      Cu.reportError("PluginCrashReporter can not read plugin information.");
+      return;
+    }
+
+    let runID = propertyBag.getPropertyAsUint32("runID");
+    let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+    let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
+    if (pluginDumpID) {
+      this.crashReports.set(runID, { pluginDumpID, browserDumpID });
+    }
+  },
+
+  /**
+   * Submit a crash report for a crashed NPAPI plugin.
+   *
+   * @param runID
+   *        The runID of the plugin that crashed. A run ID is a unique
+   *        identifier for a particular run of a plugin process - and is
+   *        analogous to a process ID (though it is managed by Gecko instead
+   *        of the operating system).
+   * @param keyVals
+   *        An object whose key-value pairs will be merged
+   *        with the ".extra" file submitted with the report.
+   *        The properties of htis object will override properties
+   *        of the same name in the .extra file.
+   */
+  submitCrashReport(runID, keyVals) {
+    if (!this.crashReports.has(runID)) {
+      Cu.reportError(`Could not find plugin dump IDs for run ID ${runID}.` +
+                     `It is possible that a report was already submitted.`);
+      return;
+    }
+
+    keyVals = keyVals || {};
+    let { pluginDumpID, browserDumpID } = this.crashReports.get(runID);
+
+    let submissionPromise = CrashSubmit.submit(pluginDumpID, {
+      recordSubmission: true,
+      extraExtraKeyVals: keyVals,
+    });
+
+    if (browserDumpID)
+      CrashSubmit.submit(browserDumpID);
+
+    this.broadcastState(runID, "submitting");
+
+    submissionPromise.then(() => {
+      this.broadcastState(runID, "success");
+    }, () => {
+      this.broadcastState(runID, "failed");
+    });
+
+    this.crashReports.delete(runID);
+  },
+
+  broadcastState(runID, state) {
+    let enumerator = Services.wm.getEnumerator("navigator:browser");
+    while (enumerator.hasMoreElements()) {
+      let window = enumerator.getNext();
+      let mm = window.messageManager;
+      mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
+                               { runID, state });
+    }
+  },
+
+  hasCrashReport(runID) {
+    return this.crashReports.has(runID);
+  },
+
+  /**
+   * Deprecated mechanism for sending crash reports for GMPs. This
+   * should be removed when bug 1146955 is fixed.
+   */
+  submitGMPCrashReport(pluginDumpID, browserDumpID) {
+    CrashSubmit.submit(pluginDumpID, { recordSubmission: true });
+    if (browserDumpID)
+      CrashSubmit.submit(browserDumpID);
+  },
+};
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -29,33 +29,54 @@ this.PluginContent = function (global) {
 
 PluginContent.prototype = {
   init: function (global) {
     this.global = global;
     // Need to hold onto the content window or else it'll get destroyed
     this.content = this.global.content;
     // Cache of plugin actions for the current page.
     this.pluginData = new Map();
+    // Cache of plugin crash information sent from the parent
+    this.pluginCrashData = new Map();
 
     // Note that the XBL binding is untrusted
     global.addEventListener("PluginBindingAttached", this, true, true);
     global.addEventListener("PluginCrashed",         this, true);
     global.addEventListener("PluginOutdated",        this, true);
     global.addEventListener("PluginInstantiated",    this, true);
     global.addEventListener("PluginRemoved",         this, true);
     global.addEventListener("pagehide",              this, true);
     global.addEventListener("pageshow",              this, true);
     global.addEventListener("unload",                this);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
+    global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
+    global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
   },
 
   uninit: function() {
+    let global = this.global;
+
+    global.removeEventListener("PluginBindingAttached", this, true);
+    global.removeEventListener("PluginCrashed",         this, true);
+    global.removeEventListener("PluginOutdated",        this, true);
+    global.removeEventListener("PluginInstantiated",    this, true);
+    global.removeEventListener("PluginRemoved",         this, true);
+    global.removeEventListener("pagehide",              this, true);
+    global.removeEventListener("pageshow",              this, true);
+    global.removeEventListener("unload",                this);
+
+    global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
+    global.removeMessageListener("BrowserPlugins:NotificationShown", this);
+    global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
+    global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
+    global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
+    global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
     delete this.content;
   },
 
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "BrowserPlugins:ActivatePlugins":
         this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
@@ -68,16 +89,29 @@ PluginContent.prototype = {
           case "play":
             this._showClickToPlayNotification(msg.objects.plugin, true);
             break;
           case "hide":
             this.hideClickToPlayOverlay(msg.objects.plugin);
             break;
         }
         break;
+      case "BrowserPlugins:NPAPIPluginProcessCrashed":
+        this.NPAPIPluginProcessCrashed({
+          pluginName: msg.data.pluginName,
+          runID: msg.data.runID,
+          state: msg.data.state,
+        });
+        break;
+      case "BrowserPlugins:CrashReportSubmitted":
+        this.NPAPIPluginCrashReportSubmitted({
+          runID: msg.data.runID,
+          state: msg.data.state,
+        })
+        break;
     }
   },
 
   onPageShow: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
@@ -92,17 +126,17 @@ PluginContent.prototype = {
 
   onPageHide: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
 
     this._finishRecordingFlashPluginTelemetry();
-    this.clearPluginDataCache();
+    this.clearPluginCaches();
   },
 
   getPluginUI: function (plugin, anonid) {
     return plugin.ownerDocument.
            getAnonymousElementByAttribute(plugin, "anonid", anonid);
   },
 
   _getPluginInfo: function (pluginElement) {
@@ -303,17 +337,17 @@ PluginContent.prototype = {
       this.onOverlayClick(event);
       return;
     }
 
     if (eventType == "PluginCrashed" &&
         !(event.target instanceof Ci.nsIObjectLoadingContent)) {
       // If the event target is not a plugin object (i.e., an <object> or
       // <embed> element), this call is for a window-global plugin.
-      this.pluginInstanceCrashed(event.target, event);
+      this.onPluginCrashed(event.target, event);
       return;
     }
 
     let plugin = event.target;
     let doc = plugin.ownerDocument;
 
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return;
@@ -334,17 +368,17 @@ PluginContent.prototype = {
         // Not all bindings have handlers
         return;
       }
     }
 
     let shouldShowNotification = false;
     switch (eventType) {
       case "PluginCrashed":
-        this.pluginInstanceCrashed(plugin, event);
+        this.onPluginCrashed(plugin, event);
         break;
 
       case "PluginNotFound": {
         /* NOP */
         break;
       }
 
       case "PluginBlocklisted":
@@ -515,34 +549,41 @@ PluginContent.prototype = {
       objLoadingContent.cancelPlayPreview();
   },
 
   // Forward a link click callback to the chrome process.
   forwardCallback: function (name) {
     this.global.sendAsyncMessage("PluginContent:LinkClickCallback", { name: name });
   },
 
-  submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
+  submitReport: function submitReport(plugin) {
     if (!AppConstants.MOZ_CRASHREPORTER) {
       return;
     }
-    let keyVals = {};
-    if (plugin) {
-      let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
-      if (userComment)
-        keyVals.PluginUserComment = userComment;
-      if (this.getPluginUI(plugin, "submitURLOptIn").checked)
-        keyVals.PluginContentURL = plugin.ownerDocument.URL;
+    if (!plugin) {
+      Cu.reportError("Attempted to submit crash report without an associated plugin.");
+      return;
+    }
+    if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
+      Cu.reportError("Attempted to submit crash report on plugin that does not" +
+                     "implement nsIObjectLoadingContent.");
+      return;
     }
 
-    this.global.sendAsyncMessage("PluginContent:SubmitReport", {
-      pluginDumpID: pluginDumpID,
-      browserDumpID: browserDumpID,
-      keyVals: keyVals,
-    });
+    let runID = plugin.runID;
+    let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn");
+    let keyVals = {};
+    let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
+    if (userComment)
+      keyVals.PluginUserComment = userComment;
+    if (this.getPluginUI(plugin, "submitURLOptIn").checked)
+      keyVals.PluginContentURL = plugin.ownerDocument.URL;
+
+    this.global.sendAsyncMessage("PluginContent:SubmitReport",
+                                 { runID, keyVals, submitURLOptIn });
   },
 
   reloadPage: function () {
     this.global.content.location.reload();
   },
 
   // Event listener for click-to-play plugins.
   _handleClickToPlayEvent: function (plugin) {
@@ -840,188 +881,207 @@ PluginContent.prototype = {
       location: location,
     }, null, principal);
   },
 
   removeNotification: function (name) {
     this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name });
   },
 
-  clearPluginDataCache: function () {
+  clearPluginCaches: function () {
     this.pluginData.clear();
+    this.pluginCrashData.clear();
   },
 
   hideNotificationBar: function (name) {
     this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name });
   },
 
-  // Crashed-plugin event listener. Called for every instance of a
-  // plugin in content.
-  pluginInstanceCrashed: function (target, aEvent) {
+  /**
+   * The PluginCrashed event handler. Note that the PluginCrashed event is
+   * fired for both NPAPI and Gecko Media plugins. In the latter case, the
+   * target of the event is the document that the GMP is being used in.
+   */
+  onPluginCrashed: function (target, aEvent) {
     if (!(aEvent instanceof this.content.PluginCrashedEvent))
       return;
 
-    let submittedReport = aEvent.submittedCrashReport;
-    let doPrompt        = true; // XXX followup for aEvent.doPrompt;
-    let submitReports   = true; // XXX followup for aEvent.submitReports;
-    let pluginName      = aEvent.pluginName;
-    let pluginDumpID    = aEvent.pluginDumpID;
-    let browserDumpID   = aEvent.browserDumpID;
-    let gmpPlugin       = aEvent.gmpPlugin;
-
-    // For non-GMP plugins, remap the plugin name to a more user-presentable form.
-    if (!gmpPlugin) {
-      pluginName = BrowserUtils.makeNicePluginName(pluginName);
+    if (aEvent.gmpPlugin) {
+      this.GMPCrashed(aEvent);
+      return;
     }
 
-    let messageString = gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
+    if (!(target instanceof Ci.nsIObjectLoadingContent))
+      return;
 
-    let plugin = null, doc;
-    if (target instanceof Ci.nsIObjectLoadingContent) {
-      plugin = target;
-      doc = plugin.ownerDocument;
-    } else {
-      doc = target.document;
-      if (!doc) {
-        return;
-      }
-      // doPrompt is specific to the crashed plugin overlay, and
-      // therefore is not applicable for window-global plugins.
-      doPrompt = false;
+    let crashData = this.pluginCrashData.get(target.runID);
+    if (!crashData) {
+      // We haven't received information from the parent yet about
+      // this crash, so we should hold off showing the crash report
+      // UI.
+      return;
     }
 
-    let status;
-    // Determine which message to show regarding crash reports.
-    if (submittedReport) { // submitReports && !doPrompt, handled in observer
-      status = "submitted";
-    }
-    else if (!submitReports && !doPrompt) {
-      status = "noSubmit";
-    }
-    else if (!pluginDumpID) {
-      // If we don't have a minidumpID, we can't (or didn't) submit anything.
-      // This can happen if the plugin is killed from the task manager.
-      status = "noReport";
-    }
-    else {
-      status = "please";
+    crashData.instances.delete(target);
+    if (crashData.instances.length == 0) {
+      this.pluginCrashData.delete(target.runID);
     }
 
-    // If we don't have a minidumpID, we can't (or didn't) submit anything.
-    // This can happen if the plugin is killed from the task manager.
-    if (!pluginDumpID) {
-        status = "noReport";
-    }
+    this.setCrashedNPAPIPluginState({
+      plugin: target,
+      state: crashData.state,
+      message: crashData.message,
+    });
+  },
+
+  NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) {
+    let message =
+      gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
+                                            [pluginName], 1);
+
+    let contentWindow = this.global.content;
+    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+    let plugins = cwu.plugins;
 
-    // If we're showing the link to manually trigger report submission, we'll
-    // want to be able to update all the instances of the UI for this crash to
-    // show an updated message when a report is submitted.
-    if (AppConstants.MOZ_CRASHREPORTER && doPrompt) {
-      let observer = {
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                               Ci.nsISupportsWeakReference]),
-        observe : (subject, topic, data) => {
-          let propertyBag = subject;
-          if (!(propertyBag instanceof Ci.nsIPropertyBag2))
-            return;
-          // Ignore notifications for other crashes.
-          if (propertyBag.get("minidumpID") != pluginDumpID)
-            return;
-          let statusDiv = this.getPluginUI(plugin, "submitStatus");
-          statusDiv.setAttribute("status", data);
-        },
-
-        handleEvent : function(event) {
-            // Not expected to be called, just here for the closure.
+    for (let plugin of plugins) {
+      if (plugin instanceof Ci.nsIObjectLoadingContent &&
+          plugin.runID == runID) {
+        // The parent has told us that the plugin process has died.
+        // It's possible that this content process hasn't yet noticed,
+        // in which case we need to stash this data around until the
+        // PluginCrashed events get sent up.
+        if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) {
+          // This plugin has already been put into the crashed state by the
+          // content process, so we can tweak its crash UI without delay.
+          this.setCrashedNPAPIPluginState({plugin, state, message});
+        } else {
+          // The content process hasn't yet determined that the plugin has crashed.
+          // Stash the data in our map, and throw the plugin into a WeakSet. When
+          // the PluginCrashed event fires on the <object>/<embed>, we'll retrieve
+          // the information we need from the Map and remove the instance from the
+          // WeakSet. Once the WeakSet is empty, we can clear the map.
+          if (!this.pluginCrashData.has(runID)) {
+            this.pluginCrashData.set(runID, {
+              state: state,
+              message: message,
+              instances: new WeakSet(),
+            });
+          }
+          let crashData = this.pluginCrashData.get(runID);
+          crashData.instances.add(plugin);
         }
       }
+    }
+  },
 
-      // Use a weak reference, so we don't have to remove it...
-      Services.obs.addObserver(observer, "crash-report-status", true);
-      // ...alas, now we need something to hold a strong reference to prevent
-      // it from being GC. But I don't want to manually manage the reference's
-      // lifetime (which should be no greater than the page).
-      // Clever solution? Use a closue with an event listener on the document.
-      // When the doc goes away, so do the listener references and the closure.
-      doc.addEventListener("mozCleverClosureHack", observer, false);
+  setCrashedNPAPIPluginState: function ({plugin, state, message}) {
+    // Force a layout flush so the binding is attached.
+    plugin.clientTop;
+    let overlay = this.getPluginUI(plugin, "main");
+    let statusDiv = this.getPluginUI(plugin, "submitStatus");
+    let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+
+    this.getPluginUI(plugin, "submitButton")
+        .addEventListener("click", (event) => {
+          if (event.button != 0 || !event.isTrusted)
+            return;
+          this.submitReport(plugin);
+        });
+
+    let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
+    optInCB.checked = pref.getBoolPref("");
+
+    statusDiv.setAttribute("status", state);
+
+    let helpIcon = this.getPluginUI(plugin, "helpIcon");
+    this.addLinkClickCallback(helpIcon, "openHelpPage");
+
+    let crashText = this.getPluginUI(plugin, "crashedText");
+    crashText.textContent = message;
+
+    let link = this.getPluginUI(plugin, "reloadLink");
+    this.addLinkClickCallback(link, "reloadPage");
+
+    let isShowing = this.shouldShowOverlay(plugin, overlay);
+
+    // Is the <object>'s size too small to hold what we want to show?
+    if (!isShowing) {
+      // First try hiding the crash report submission UI.
+      statusDiv.removeAttribute("status");
+
+      isShowing = this.shouldShowOverlay(plugin, overlay);
     }
-
-    let isShowing = false;
+    this.setVisibility(plugin, overlay, isShowing);
 
-    if (plugin) {
-      // If there's no plugin (an <object> or <embed> element), this call is
-      // for a window-global plugin. In this case, there's no overlay to show.
-      isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt);
-    }
+    let doc = plugin.ownerDocument;
+    let runID = plugin.runID;
 
     if (isShowing) {
       // If a previous plugin on the page was too small and resulted in adding a
       // notification bar, then remove it because this plugin instance it big
       // enough to serve as in-content notification.
       this.hideNotificationBar("plugin-crashed");
       doc.mozNoPluginCrashedNotification = true;
     } else {
       // If another plugin on the page was large enough to show our UI, we don't
       // want to show a notification bar.
       if (!doc.mozNoPluginCrashedNotification) {
-        this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", {
-          messageString: messageString,
-          pluginDumpID: pluginDumpID,
-          browserDumpID: browserDumpID,
-        });
+        this.global.sendAsyncMessage("PluginContent:ShowNPAPIPluginCrashedNotification",
+                                     { message, runID });
         // Remove the notification when the page is reloaded.
         doc.defaultView.top.addEventListener("unload", event => {
           this.hideNotificationBar("plugin-crashed");
         }, false);
       }
     }
+  },
 
-    // Configure the crashed-plugin placeholder.
-    // Returns true if the plugin overlay is visible.
-    function _setUpPluginOverlay(plugin, doPromptSubmit) {
-      if (!plugin) {
-        return false;
-      }
-
-      // Force a layout flush so the binding is attached.
-      plugin.clientTop;
-      let overlay = this.getPluginUI(plugin, "main");
-      let statusDiv = this.getPluginUI(plugin, "submitStatus");
+  NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
+    this.pluginCrashData.delete(runID);
+    let contentWindow = this.global.content;
+    let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+    let plugins = cwu.plugins;
 
-      if (doPromptSubmit) {
-        this.getPluginUI(plugin, "submitButton").addEventListener("click",
-        function (event) {
-          if (event.button != 0 || !event.isTrusted)
-            return;
-          this.submitReport(pluginDumpID, browserDumpID, plugin);
-          pref.setBoolPref("", optInCB.checked);
-        }.bind(this));
-        let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
-        let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
-        optInCB.checked = pref.getBoolPref("");
+    for (let plugin of plugins) {
+      if (plugin instanceof Ci.nsIObjectLoadingContent &&
+          plugin.runID == runID) {
+        let statusDiv = this.getPluginUI(plugin, "submitStatus");
+        statusDiv.setAttribute("status", state);
       }
+    }
+  },
 
-      statusDiv.setAttribute("status", status);
-
-      let helpIcon = this.getPluginUI(plugin, "helpIcon");
-      this.addLinkClickCallback(helpIcon, "openHelpPage");
-
-      let crashText = this.getPluginUI(plugin, "crashedText");
-      crashText.textContent = messageString;
-
-      let link = this.getPluginUI(plugin, "reloadLink");
-      this.addLinkClickCallback(link, "reloadPage");
-
-      let isShowing = this.shouldShowOverlay(plugin, overlay);
+  /**
+   * Currently, GMP crash events are only handled in the non-e10s case.
+   * e10s support for GMP crash events is being tracked in bug 1146955.
+   */
+  GMPCrashed: function(aEvent) {
+    let target          = aEvent.target;
+    let submittedReport = aEvent.submittedCrashReport;
+    let pluginName      = aEvent.pluginName;
+    let pluginDumpID    = aEvent.pluginDumpID;
+    let browserDumpID   = aEvent.browserDumpID;
+    let gmpPlugin       = aEvent.gmpPlugin;
+    let doc             = target.document;
 
-      // Is the <object>'s size too small to hold what we want to show?
-      if (!isShowing) {
-        // First try hiding the crash report submission UI.
-        statusDiv.removeAttribute("status");
+    if (!gmpPlugin || !doc) {
+      // TODO: Throw exception? How did we get here?
+      return;
+    }
+
+    let messageString =
+      gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
+                                            [pluginName], 1);
 
-        isShowing = this.shouldShowOverlay(plugin, overlay);
-      }
-      this.setVisibility(plugin, overlay, isShowing);
+    this.global.sendAsyncMessage("PluginContent:ShowGMPCrashedNotification", {
+      messageString: messageString,
+      pluginDumpID: pluginDumpID,
+      browserDumpID: browserDumpID,
+    });
 
-      return isShowing;
-    }
-  }
+    // Remove the notification when the page is reloaded.
+    doc.defaultView.top.addEventListener("unload", event => {
+      this.hideNotificationBar("plugin-crashed");
+    }, false);
+  },
 };
deleted file mode 100644
--- a/browser/modules/TabCrashReporter.jsm
+++ /dev/null
@@ -1,101 +0,0 @@
-/* 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";
-
-let Cc = Components.classes;
-let Ci = Components.interfaces;
-let Cu = Components.utils;
-
-this.EXPORTED_SYMBOLS = [ "TabCrashReporter" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
-  "resource://gre/modules/CrashSubmit.jsm");
-
-this.TabCrashReporter = {
-  init: function () {
-    if (this.initialized)
-      return;
-    this.initialized = true;
-
-    Services.obs.addObserver(this, "ipc:content-shutdown", false);
-    Services.obs.addObserver(this, "oop-frameloader-crashed", false);
-
-    this.childMap = new Map();
-    this.browserMap = new WeakMap();
-  },
-
-  observe: function (aSubject, aTopic, aData) {
-    switch (aTopic) {
-      case "ipc:content-shutdown":
-        aSubject.QueryInterface(Ci.nsIPropertyBag2);
-
-        if (!aSubject.get("abnormal"))
-          return;
-
-        this.childMap.set(aSubject.get("childID"), aSubject.get("dumpID"));
-        break;
-
-      case "oop-frameloader-crashed":
-        aSubject.QueryInterface(Ci.nsIFrameLoader);
-
-        let browser = aSubject.ownerElement;
-        if (!browser)
-          return;
-
-        this.browserMap.set(browser, aSubject.childID);
-        break;
-    }
-  },
-
-  submitCrashReport: function (aBrowser) {
-    let childID = this.browserMap.get(aBrowser);
-    let dumpID = this.childMap.get(childID);
-    if (!dumpID)
-      return
-
-    if (CrashSubmit.submit(dumpID, { recordSubmission: true })) {
-      this.childMap.set(childID, null); // Avoid resubmission.
-      this.removeSubmitCheckboxesForSameCrash(childID);
-    }
-  },
-
-  removeSubmitCheckboxesForSameCrash: function(childID) {
-    let enumerator = Services.wm.getEnumerator("navigator:browser");
-    while (enumerator.hasMoreElements()) {
-      let window = enumerator.getNext();
-      if (!window.gMultiProcessBrowser)
-        continue;
-
-      for (let browser of window.gBrowser.browsers) {
-        if (browser.isRemoteBrowser)
-          continue;
-
-        let doc = browser.contentDocument;
-        if (!doc.documentURI.startsWith("about:tabcrashed"))
-          continue;
-
-        if (this.browserMap.get(browser) == childID) {
-          this.browserMap.delete(browser);
-          browser.contentDocument.documentElement.classList.remove("crashDumpAvailable");
-          browser.contentDocument.documentElement.classList.add("crashDumpSubmitted");
-        }
-      }
-    }
-  },
-
-  onAboutTabCrashedLoad: function (aBrowser) {
-    if (!this.childMap)
-      return;
-
-    let dumpID = this.childMap.get(this.browserMap.get(aBrowser));
-    if (!dumpID)
-      return;
-
-    aBrowser.contentDocument.documentElement.classList.add("crashDumpAvailable");
-  }
-}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -11,16 +11,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 ]
 
 EXTRA_JS_MODULES += [
     'AboutHome.jsm',
     'BrowserUITelemetry.jsm',
     'CastingApps.jsm',
     'Chat.jsm',
     'ContentClick.jsm',
+    'ContentCrashReporters.jsm',
     'ContentLinkHandler.jsm',
     'ContentObservers.jsm',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
     'CustomizationTabPreloader.jsm',
     'DirectoryLinksProvider.jsm',
     'E10SUtils.jsm',
     'Feeds.jsm',
@@ -33,17 +34,16 @@ EXTRA_JS_MODULES += [
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
-    'TabCrashReporter.jsm',
     'WebappManager.jsm',
     'webrtcUI.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES += [
         'Windows8WindowFrameColor.jsm',
         'WindowsJumpLists.jsm',