Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 09 Apr 2015 22:57:56 -0400
changeset 268319 fec90cbfbaad34e7a7e2c1aea1f6cdee52d9dd12
parent 268288 80b261ea54fc5e6ab64e92e25d06c91085f19fdf (current diff)
parent 268318 f74e35a6a1e4dde4cc9d380089386427b77957b0 (diff)
child 268320 9c079c16a3993d76ace7479f43a9fb320ebc6292
child 268378 b2e2a9ff51e92d723537c8a878201727313b60c3
child 268428 3579d599dea351fd9886c129b27c96aececef85e
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
fec90cbfbaad / 40.0a1 / 20150410030204 / files
nightly linux64
fec90cbfbaad / 40.0a1 / 20150410030204 / files
nightly mac
fec90cbfbaad / 40.0a1 / 20150410030204 / files
nightly win32
fec90cbfbaad / 40.0a1 / 20150410030204 / files
nightly win64
fec90cbfbaad / 40.0a1 / 20150410030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c. a=merge
browser/modules/TabCrashReporter.jsm
browser/themes/linux/devtools/commandline.css
browser/themes/osx/devtools/commandline.css
browser/themes/windows/devtools/commandline.css
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -653,16 +653,17 @@ pref("accessibility.typeaheadfind.flashB
 
 // plugin finder service url
 pref("pfs.datasource.url", "https://pfs.mozilla.org/plugins/PluginFinderService.php?mimetype=%PLUGIN_MIMETYPE%&appID=%APP_ID%&appVersion=%APP_VERSION%&clientOS=%CLIENT_OS%&chromeLocale=%CHROME_LOCALE%&appRelease=%APP_RELEASE%");
 
 pref("plugins.update.url", "https://www.mozilla.org/%LOCALE%/plugincheck/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=plugincheck-update");
 pref("plugins.update.notifyUser", false);
 
 pref("plugins.click_to_play", true);
+pref("plugins.testmode", false);
 
 pref("plugin.default.state", 1);
 
 // Plugins bundled in XPIs are enabled by default.
 pref("plugin.defaultXpi.state", 2);
 
 // Flash is enabled by default, and Java is click-to-activate by default on
 // all channels.
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -416,9 +416,10 @@
                 accesskey="&inspectContextMenu.accesskey;"
                 oncommand="gContextMenu.inspectNode();"/>
       <menuseparator id="context-media-eme-separator" hidden="true"/>
       <menuitem id="context-media-eme-learnmore"
                 class="menuitem-iconic"
                 hidden="true"
                 label="&emeLearnMoreContextMenu.label;"
                 accesskey="&emeLearnMoreContextMenu.accesskey;"
-                onclick="gContextMenu.drmLearnMore(event);"/>
+                oncommand="gContextMenu.drmLearnMore(event);"
+                onclick="checkForMiddleClick(this, event);"/>
--- 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/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -73,8 +73,10 @@ skip-if = os == 'linux' || os == 'mac' #
 [browser_pageInfo_plugins.js]
 [browser_pluginnotification.js]
 [browser_pluginplaypreview.js]
 [browser_pluginplaypreview2.js]
 [browser_pluginplaypreview3.js]
 [browser_pluginCrashCommentAndURL.js]
 skip-if = !crashreporter
 [browser_plugins_added_dynamically.js]
+[browser_pluginCrashReportNonDeterminism.js]
+skip-if = !crashreporter || os == 'linux' # Bug 1152811
--- a/browser/base/content/test/plugins/browser_CTP_crashreporting.js
+++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
@@ -1,207 +1,166 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-Cu.import("resource://gre/modules/Services.jsm");
-
 const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
-const gTestRoot = getRootDirectory(gTestPath);
-var gTestBrowser = null;
+const PLUGIN_PAGE = getRootDirectory(gTestPath) + "plugin_big.html";
 
 /**
- * Frame script that will be injected into the test browser
- * to cause the crash, and then manipulate the crashed plugin
- * UI. Specifically, after the crash, we ensure that the
- * crashed plugin UI is using the right style rules and that
- * the submit URL opt-in defaults to checked. Then, we fill in
- * a comment with the crash report, uncheck the submit URL
- * opt-in, and send the crash reports.
+ * Takes an nsIPropertyBag and converts it into a JavaScript Object. It
+ * will also convert any nsIPropertyBag's within the nsIPropertyBag
+ * recursively.
+ *
+ * @param aBag
+ *        The nsIPropertyBag to convert.
+ * @return Object
+ *        Keyed on the names of the nsIProperty's within the nsIPropertyBag,
+ *        and mapping to their values.
  */
-function frameScript() {
-  function fail(reason) {
-    sendAsyncMessage("test:crash-plugin:fail", {
-      reason: `Failure from frameScript: ${reason}`,
-    });
+function convertPropertyBag(aBag) {
+  let result = {};
+  let enumerator = aBag.enumerator;
+  while(enumerator.hasMoreElements()) {
+    let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+    if (value instanceof Ci.nsIPropertyBag) {
+      value = convertPropertyBag(value);
+    }
+    result[name] = value;
   }
-
-  addMessageListener("test:crash-plugin", () => {
-    let doc = content.document;
-
-    addEventListener("PluginCrashed", (event) => {
-      let plugin = doc.getElementById("test");
-      if (!plugin) {
-        fail("Could not find plugin element");
-        return;
-      }
-
-      let getUI = (anonid) => {
-        return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
-      };
+  return result;
+}
 
-      let style = content.getComputedStyle(getUI("pleaseSubmit"));
-      if (style.display != "block") {
-        fail("Submission UI visibility is not correct. Expected block, "
-             + " got " + style.display);
-        return;
-      }
-
-      getUI("submitComment").value = "a test comment";
-      if (!getUI("submitURLOptIn").checked) {
-        fail("URL opt-in should default to true.");
-        return;
-      }
-
-      getUI("submitURLOptIn").click();
-      getUI("submitButton").click();
-    });
-
-    let plugin = doc.getElementById("test");
-    try {
-      plugin.crash()
-    } catch(e) {
-    }
-  });
-
-  addMessageListener("test:plugin-submit-status", () => {
-    let doc = content.document;
-    let plugin = doc.getElementById("test");
-    let submitStatusEl =
-      doc.getAnonymousElementByAttribute(plugin, "anonid", "submitStatus");
-    let submitStatus = submitStatusEl.getAttribute("status");
-    sendAsyncMessage("test:plugin-submit-status:result", {
-      submitStatus: submitStatus,
-    });
+function promisePopupNotificationShown(notificationID) {
+  return new Promise((resolve) => {
+    waitForNotificationShown(notificationID, resolve);
   });
 }
 
-// Test that plugin crash submissions still work properly after
-// click-to-play activation.
-
-function test() {
-  // Crashing the plugin takes up a lot of time, so extend the test timeout.
-  requestLongerTimeout(2);
-  waitForExplicitFinish();
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play activation.
+ */
+add_task(function*() {
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
 
   // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
   // crash reports.  This test needs them enabled.  The test also needs a mock
   // report server, and fortunately one is already set up by toolkit/
   // crashreporter/test/Makefile.in.  Assign its URL to MOZ_CRASHREPORTER_URL,
   // which CrashSubmit.jsm uses as a server override.
   let env = Cc["@mozilla.org/process/environment;1"].
             getService(Components.interfaces.nsIEnvironment);
   let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
   let serverURL = env.get("MOZ_CRASHREPORTER_URL");
   env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
   env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
 
-  let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
-  gTestBrowser = gBrowser.getBrowserForTab(tab);
-  let mm = gTestBrowser.messageManager;
-  mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
-  mm.addMessageListener("test:crash-plugin:fail", (message) => {
-    ok(false, message.data.reason);
-  });
-
-  gTestBrowser.addEventListener("load", onPageLoad, true);
-  Services.obs.addObserver(onSubmitStatus, "crash-report-status", false);
-
   registerCleanupFunction(function cleanUp() {
     env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
     env.set("MOZ_CRASHREPORTER_URL", serverURL);
-    gTestBrowser.removeEventListener("load", onPageLoad, true);
-    Services.obs.removeObserver(onSubmitStatus, "crash-report-status");
-    gBrowser.removeCurrentTab();
   });
 
-  gTestBrowser.contentWindow.location = gTestRoot + "plugin_big.html";
-}
-function onPageLoad() {
-  // Force the plugins binding to attach as layout is async.
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  plugin.clientTop;
-  executeSoon(afterBindingAttached);
-}
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PLUGIN_PAGE,
+  }, function* (browser) {
+    let activated = yield ContentTask.spawn(browser, null, function*() {
+      let plugin = content.document.getElementById("test");
+      return plugin.QueryInterface(Ci.nsIObjectLoadingContent).activated;
+    });
+    ok(!activated, "Plugin should not be activated");
+
+    // Open up the click-to-play notification popup...
+    let popupNotification = PopupNotifications.getNotification("click-to-play-plugins",
+                                                               browser);
+    ok(popupNotification, "Should have a click-to-play notification");
+
+    yield promisePopupNotificationShown(popupNotification);
+
+    // The primary button in the popup should activate the plugin.
+    PopupNotifications.panel.firstChild._primaryButton.click();
 
-function afterBindingAttached() {
-  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
-  ok(popupNotification, "Should have a click-to-play notification");
+    // Prepare a crash report topic observer that only returns when
+    // the crash report has been successfully sent.
+    let crashReportChecker = (subject, data) => {
+      return (data == "success");
+    };
+    let crashReportPromise = TestUtils.topicObserved("crash-report-status",
+                                                     crashReportChecker);
 
-  let plugin = gTestBrowser.contentDocument.getElementById("test");
-  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-  ok(!objLoadingContent.activated, "Plugin should not be activated");
+    yield ContentTask.spawn(browser, null, function*() {
+      let plugin = content.document.getElementById("test");
+      plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 
-  // Simulate clicking the "Allow Always" button.
-  waitForNotificationShown(popupNotification, function() {
-    PopupNotifications.panel.firstChild._primaryButton.click();
-    let condition = function() objLoadingContent.activated;
-    waitForCondition(condition, pluginActivated, "Waited too long for plugin to activate");
-  });
-}
+      yield ContentTaskUtils.waitForCondition(() => {
+        return plugin.activated;
+      }, "Waited too long for plugin to activate.");
+
+      try {
+        plugin.crash();
+      } catch(e) {}
+
+      let doc = plugin.ownerDocument;
+
+      let getUI = (anonid) => {
+        return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
+      };
+
+      // Now wait until the plugin crash report UI shows itself, which is
+      // asynchronous.
+      let statusDiv;
 
-function pluginActivated() {
-  let mm = gTestBrowser.messageManager;
-  mm.sendAsyncMessage("test:crash-plugin");
-}
+      yield ContentTaskUtils.waitForCondition(() => {
+        statusDiv = getUI("submitStatus");
+        return statusDiv.getAttribute("status") == "please";
+      }, "Waited too long for plugin to show crash report UI");
+
+      // Make sure the UI matches our expectations...
+      let style = content.getComputedStyle(getUI("pleaseSubmit"));
+      if (style.display != "block") {
+        return Promise.reject(`Submission UI visibility is not correct. ` +
+                              `Expected block style, got ${style.display}.`);
+      }
 
-function onSubmitStatus(subj, topic, data) {
-  try {
-    // Wait for success or failed, doesn't matter which.
-    if (data != "success" && data != "failed")
-      return;
+      // Fill the crash report in with some test values that we'll test for in
+      // the parent.
+      getUI("submitComment").value = "a test comment";
+      let optIn = getUI("submitURLOptIn");
+      if (!optIn.checked) {
+        return Promise.reject("URL opt-in should default to true.");
+      }
+
+      // Submit the report.
+      optIn.click();
+      getUI("submitButton").click();
 
-    let propBag = subj.QueryInterface(Ci.nsIPropertyBag);
-    if (data == "success") {
-      let remoteID = getPropertyBagValue(propBag, "serverCrashID");
-      ok(!!remoteID, "serverCrashID should be set");
+      // And wait for the parent to say that the crash report was submitted
+      // successfully.
+      yield ContentTaskUtils.waitForCondition(() => {
+        return statusDiv.getAttribute("status") == "success";
+      }, "Timed out waiting for plugin binding to be in success state");
+    });
+
+    let [subject, data] = yield crashReportPromise;
+
+    ok(subject instanceof Ci.nsIPropertyBag,
+       "The crash report subject should be an nsIPropertyBag.");
 
-      // Remove the submitted report file.
-      let file = Cc["@mozilla.org/file/local;1"]
-                   .createInstance(Ci.nsILocalFile);
-      file.initWithPath(Services.crashmanager._submittedDumpsDir);
-      file.append(remoteID + ".txt");
-      ok(file.exists(), "Submitted report file should exist");
-      file.remove(false);
-    }
+    let crashData = convertPropertyBag(subject);
+    ok(crashData.serverCrashID, "Should have a serverCrashID set.");
 
-    let extra = getPropertyBagValue(propBag, "extra");
-    ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
+    // Remove the submitted report file after ensuring it exists.
+    let file = Cc["@mozilla.org/file/local;1"]
+                 .createInstance(Ci.nsILocalFile);
+    file.initWithPath(Services.crashmanager._submittedDumpsDir);
+    file.append(crashData.serverCrashID + ".txt");
+    ok(file.exists(), "Submitted report file should exist");
+    file.remove(false);
 
-    let val = getPropertyBagValue(extra, "PluginUserComment");
-    is(val, "a test comment",
+    ok(crashData.extra, "Extra data should exist");
+    is(crashData.extra.PluginUserComment, "a test comment",
        "Comment in extra data should match comment in textbox");
 
-    val = getPropertyBagValue(extra, "PluginContentURL");
-    ok(val === undefined,
+    is(crashData.extra.PluginContentURL, undefined,
        "URL should be absent from extra data when opt-in not checked");
-
-    let submitStatus = null;
-    let mm = gTestBrowser.messageManager;
-    mm.addMessageListener("test:plugin-submit-status:result", (message) => {
-      submitStatus = message.data.submitStatus;
-    });
-
-    mm.sendAsyncMessage("test:plugin-submit-status");
-
-    let condition = () => submitStatus;
-    waitForCondition(condition, () => {
-      is(submitStatus, data, "submitStatus data should match");
-      finish();
-    }, "Waiting for submitStatus to be reported from frame script");
-  }
-  catch (err) {
-    failWithException(err);
-  }
-}
-
-function getPropertyBagValue(bag, key) {
-  try {
-    var val = bag.getProperty(key);
-  }
-  catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
-  return val;
-}
-
-function failWithException(err) {
-  ok(false, "Uncaught exception: " + err + "\n" + err.stack);
-}
+  });
+});
--- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -1,62 +1,40 @@
 /* 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/. */
 
-let gTestBrowser = null;
-
-let crashedEventProperties = {
-  pluginName: "GlobalTestPlugin",
-  pluginDumpID: "1234",
-  browserDumpID: "5678",
-  submittedCrashReport: false,
-  bubbles: true,
-  cancelable: true
-}
-
-// Test that plugin crash submissions still work properly after
-// click-to-play activation.
-
-function test() {
-  waitForExplicitFinish();
-  let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
-  gTestBrowser = gBrowser.getBrowserForTab(tab);
-  gTestBrowser.addEventListener("PluginCrashed", onCrash, false);
-  gTestBrowser.addEventListener("load", onPageLoad, true);
-
-  registerCleanupFunction(function cleanUp() {
-    gTestBrowser.removeEventListener("PluginCrashed", onCrash, false);
-    gTestBrowser.removeEventListener("load", onPageLoad, true);
-    gBrowser.removeTab(tab);
-  });
-}
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play activation.
+ */
+add_task(function*() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: "about:blank",
+  }, function* (browser) {
+    yield ContentTask.spawn(browser, null, function* () {
+      const GMP_CRASH_EVENT = {
+        pluginName: "GlobalTestPlugin",
+        pluginDumpID: "1234",
+        browserDumpID: "5678",
+        submittedCrashReport: false,
+        bubbles: true,
+        cancelable: true,
+        gmpPlugin: true,
+      };
 
-function onPageLoad() {
-  executeSoon(generateCrashEvent);
-}
+      let crashEvent = new content.PluginCrashedEvent("PluginCrashed",
+                                                      GMP_CRASH_EVENT);
+      content.dispatchEvent(crashEvent);
+    });
 
-function generateCrashEvent() {
-  let window = gTestBrowser.contentWindow;
-  let crashedEvent = new window.PluginCrashedEvent("PluginCrashed", crashedEventProperties);
-
-  window.dispatchEvent(crashedEvent);
-}
-
+    let notification = yield waitForNotificationBar("plugin-crashed", browser);
 
-function onCrash(event) {
-  let target = event.target;
-  is (target, gTestBrowser.contentWindow, "Event target is the window.");
-
-  for (let [name, val] of Iterator(crashedEventProperties)) {
-    let propVal = event[name];
-
-    is (propVal, val, "Correct property: " + name + ".");
-  }
-
-  waitForNotificationBar("plugin-crashed", gTestBrowser, (notification) => {
-    let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+    let notificationBox = gBrowser.getNotificationBox(browser);
     ok(notification, "Infobar was shown.");
-    is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority.");
-    is(notification.getAttribute("label"), "The GlobalTestPlugin plugin has crashed.", "Correct message.");
-    finish();
+    is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
+       "Correct priority.");
+    is(notification.getAttribute("label"),
+       "The GlobalTestPlugin plugin has crashed.",
+       "Correct message.");
   });
-}
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
@@ -0,0 +1,259 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * With e10s, plugins must run in their own process. This means we have
+ * three processes at a minimum when we're running a plugin:
+ *
+ * 1) The main browser, or "chrome" process
+ * 2) The content process hosting the plugin instance
+ * 3) The plugin process
+ *
+ * If the plugin process crashes, we cannot be sure if the chrome process
+ * will hear about it first, or the content process will hear about it
+ * first. Because of how IPC works, that's really up to the operating system,
+ * and we assume any guarantees about it, so we have to account for both
+ * possibilities.
+ *
+ * This test exercises the browser's reaction to both possibilities.
+ */
+
+const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html";
+const CRASHED_MESSAGE = "BrowserPlugins:NPAPIPluginProcessCrashed";
+
+/**
+ * In order for our test to work, we need to be able to put a plugin
+ * in a very specific state. Specifically, we need it to match the
+ * :-moz-handler-crashed pseudoselector. The only way I can find to
+ * do that is by actually crashing the plugin. So we wait for the
+ * plugin to crash and show the "please" state (since that will
+ * only show if both the message from the parent has been received
+ * AND the PluginCrashed event has fired).
+ *
+ * Once in that state, we try to rewind the clock a little bit - we clear
+ * out the crashData cache in the PluginContent with a message, and we also
+ * override the pluginFallbackState of the <object> to fool PluginContent
+ * into believing that the plugin is in a particular state.
+ *
+ * @param browser
+ *        The browser that has loaded the CRASH_URL that we need to
+ *        prepare to be in the special state.
+ * @param pluginFallbackState
+ *        The value we should override the <object>'s pluginFallbackState
+ *        with.
+ * @return Promise
+ *        The Promise resolves when the plugin has officially been put into
+ *        the crash reporter state, and then "rewound" to have the "status"
+ *        attribute of the statusDiv removed. The resolved Promise returns
+ *        the run ID for the crashed plugin. It rejects if we never get into
+ *        the crash reporter state.
+ */
+function preparePlugin(browser, pluginFallbackState) {
+  return ContentTask.spawn(browser, pluginFallbackState, function* (pluginFallbackState) {
+    let plugin = content.document.getElementById("plugin");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    // CRASH_URL will load a plugin that crashes immediately. We
+    // wait until the plugin has finished being put into the crash
+    // state.
+    let statusDiv;
+    yield ContentTaskUtils.waitForCondition(() => {
+      statusDiv = plugin.ownerDocument
+                        .getAnonymousElementByAttribute(plugin, "anonid",
+                                                        "submitStatus");
+      return statusDiv && statusDiv.getAttribute("status") == "please";
+    }, "Timed out waiting for plugin to be in crash report state");
+
+    // "Rewind", by wiping out the status attribute...
+    statusDiv.removeAttribute("status");
+    // Somehow, I'm able to get away with overriding the getter for
+    // this XPCOM object. Probably because I've got chrome privledges.
+    Object.defineProperty(plugin, "pluginFallbackType", {
+      get: function() {
+        return pluginFallbackState;
+      }
+    });
+    return plugin.runID;
+  }).then((runID) => {
+    browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData");
+    return runID;
+  });
+}
+
+add_task(function* setup() {
+  // Bypass click-to-play
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+  // Clear out any minidumps we create from plugins - we really don't care
+  // about them.
+  let crashObserver = (subject, topic, data) => {
+    if (topic != "plugin-crashed") {
+      return;
+    }
+
+    let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+    let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
+
+    let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    minidumpDir.append("minidumps");
+
+    let pluginDumpFile = minidumpDir.clone();
+    pluginDumpFile.append(minidumpID + ".dmp");
+
+    let extraFile = minidumpDir.clone();
+    extraFile.append(minidumpID + ".extra");
+
+    ok(pluginDumpFile.exists(), "Found minidump");
+    ok(extraFile.exists(), "Found extra file");
+
+    pluginDumpFile.remove(false);
+    extraFile.remove(false);
+  };
+
+  Services.obs.addObserver(crashObserver, "plugin-crashed");
+  // plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
+  Services.prefs.setBoolPref("plugins.testmode", true);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("plugins.testmode");
+    Services.obs.removeObserver(crashObserver, "plugin-crashed");
+  });
+});
+
+/**
+ * In this case, the chrome process hears about the crash first.
+ */
+add_task(function* testChromeHearsPluginCrashFirst() {
+  // Open a remote window so that we can run this test even if e10s is not
+  // enabled by default.
+  let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+  let browser = win.gBrowser.selectedBrowser;
+
+  browser.loadURI(CRASH_URL);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  // In this case, we want the <object> to match the -moz-handler-crashed
+  // pseudoselector, but we want it to seem still active, because the
+  // content process is not yet supposed to know that the plugin has
+  // crashed.
+  let runID = yield preparePlugin(browser,
+                                  Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE);
+
+  // Send the message down to PluginContent.jsm saying that the plugin has
+  // crashed, and that we have a crash report.
+  let mm = browser.messageManager;
+  mm.sendAsyncMessage(CRASHED_MESSAGE,
+                      { pluginName: "", runID, state: "please" });
+
+  let [gotExpected, msg] = yield ContentTask.spawn(browser, {}, function* () {
+    // At this point, the content process should have heard the
+    // plugin crash message from the parent, and we are OK to emit
+    // the PluginCrashed event.
+    let plugin = content.document.getElementById("plugin");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    let statusDiv = plugin.ownerDocument
+                          .getAnonymousElementByAttribute(plugin, "anonid",
+                                                          "submitStatus");
+
+    if (statusDiv.getAttribute("status") == "please") {
+      return [false, "Did not expect plugin to be in crash report mode yet."];
+    }
+
+    // Now we need the plugin to seem crashed to PluginContent.jsm, without
+    // actually crashing the plugin again. We hack around this by overriding
+    // the pluginFallbackType again.
+    Object.defineProperty(plugin, "pluginFallbackType", {
+      get: function() {
+        return Ci.nsIObjectLoadingContent.PLUGIN_CRASHED;
+      },
+    });
+
+    let event = new content.PluginCrashedEvent("PluginCrashed", {
+      pluginName: "",
+      pluginDumpID: "",
+      browserDumpID: "",
+      submittedCrashReport: false,
+      bubbles: true,
+      cancelable: true,
+    });
+
+    plugin.dispatchEvent(event);
+    return [statusDiv.getAttribute("status") == "please",
+            "Should have been showing crash report UI"];
+  });
+
+  ok(gotExpected, msg);
+  yield BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * In this case, the content process hears about the crash first.
+ */
+add_task(function* testContentHearsCrashFirst() {
+  // Open a remote window so that we can run this test even if e10s is not
+  // enabled by default.
+  let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+  let browser = win.gBrowser.selectedBrowser;
+
+  browser.loadURI(CRASH_URL);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  // In this case, we want the <object> to match the -moz-handler-crashed
+  // pseudoselector, and we want the plugin to seem crashed, since the
+  // content process in this case has heard about the crash first.
+  let runID = yield preparePlugin(browser,
+                                  Ci.nsIObjectLoadingContent.PLUGIN_CRASHED);
+
+  let [gotExpected, msg] = yield ContentTask.spawn(browser, null, function* () {
+    // At this point, the content process has not yet heard from the
+    // parent about the crash report. Let's ensure that by making sure
+    // we're not showing the plugin crash report UI.
+    let plugin = content.document.getElementById("plugin");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    let statusDiv = plugin.ownerDocument
+                          .getAnonymousElementByAttribute(plugin, "anonid",
+                                                          "submitStatus");
+
+    if (statusDiv.getAttribute("status") == "please") {
+      return [false, "Did not expect plugin to be in crash report mode yet."];
+    }
+
+    let event = new content.PluginCrashedEvent("PluginCrashed", {
+      pluginName: "",
+      pluginDumpID: "",
+      browserDumpID: "",
+      submittedCrashReport: false,
+      bubbles: true,
+      cancelable: true,
+    });
+
+    plugin.dispatchEvent(event);
+
+    return [statusDiv.getAttribute("status") != "please",
+            "Should not yet be showing crash report UI"];
+  });
+
+  ok(gotExpected, msg);
+
+  // Now send the message down to PluginContent.jsm that the plugin has
+  // crashed...
+  let mm = browser.messageManager;
+  mm.sendAsyncMessage(CRASHED_MESSAGE,
+                      { pluginName: "", runID, state: "please"});
+
+  [gotExpected, msg] = yield ContentTask.spawn(browser, null, function* () {
+    // At this point, the content process will have heard the message
+    // from the parent and reacted to it. We should be showing the plugin
+    // crash report UI now.
+    let plugin = content.document.getElementById("plugin");
+    plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    let statusDiv = plugin.ownerDocument
+                          .getAnonymousElementByAttribute(plugin, "anonid",
+                                                          "submitStatus");
+
+    return [statusDiv.getAttribute("status") == "please",
+            "Should have been showing crash report UI"];
+  });
+
+  ok(gotExpected, msg);
+
+  yield BrowserTestUtils.closeWindow(win);
+});
--- 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") &&
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -55,16 +55,17 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = 
   searchEnabled: true,
   editableValueTooltip: "",
   editableNameTooltip: "",
   preventDisableOnChange: true,
   preventDescriptorModifiers: true,
   eval: () => {}
 };
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
+const FREETEXT_FILTER_SEARCH_DELAY = 200; // ms
 
 /**
  * Object defining the network monitor view components.
  */
 let NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
@@ -345,16 +346,17 @@ RequestsMenuView.prototype = Heritage.ex
    */
   initialize: function() {
     dumpn("Initializing the RequestsMenuView");
 
     this.widget = new SideMenuWidget($("#requests-menu-contents"));
     this._splitter = $("#network-inspector-view-splitter");
     this._summary = $("#requests-menu-network-summary-label");
     this._summary.setAttribute("value", L10N.getStr("networkMenu.empty"));
+    this.userInputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     Prefs.filters.forEach(type => this.filterOn(type));
     this.sortContents(this._byTiming);
 
     this.allowFocusOnRightClick = true;
     this.maintainSelectionVisible = true;
     this.widget.autoscrollWithAppendedItems = true;
 
@@ -375,16 +377,23 @@ RequestsMenuView.prototype = Heritage.ex
     this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
     this._onReloadCommand = () => NetMonitorView.reloadPage();
 
     this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
     this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
     this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
     this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
 
+    this.requestsFreetextFilterEvent = this.requestsFreetextFilterEvent.bind(this);
+    this.reFilterRequests = this.reFilterRequests.bind(this);
+
+    this.freetextFilterBox = $("#requests-menu-filter-freetext-text");
+    this.freetextFilterBox.addEventListener("input", this.requestsFreetextFilterEvent, false);
+    this.freetextFilterBox.addEventListener("command", this.requestsFreetextFilterEvent, false);
+
     $("#toolbar-labels").addEventListener("click", this.requestsMenuSortEvent, false);
     $("#requests-menu-footer").addEventListener("click", this.requestsMenuFilterEvent, false);
     $("#requests-menu-clear-button").addEventListener("click", this.reqeustsMenuClearEvent, false);
     $("#network-request-popup").addEventListener("popupshowing", this._onContextShowing, false);
     $("#request-menu-context-newtab").addEventListener("command", this._onContextNewTabCommand, false);
     $("#request-menu-context-copy-url").addEventListener("command", this._onContextCopyUrlCommand, false);
     $("#request-menu-context-copy-image-as-data-uri").addEventListener("command", this._onContextCopyImageAsDataUriCommand, false);
     $("#toggle-raw-headers").addEventListener("click", this.toggleRawHeadersEvent, false);
@@ -435,16 +444,19 @@ RequestsMenuView.prototype = Heritage.ex
     this.widget.removeEventListener("select", this._onSelect, false);
     this.widget.removeEventListener("swap", this._onSwap, false);
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
 
     $("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
     $("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
     $("#requests-menu-clear-button").removeEventListener("click", this.reqeustsMenuClearEvent, false);
+    this.freetextFilterBox.removeEventListener("input", this.requestsFreetextFilterEvent, false);
+    this.freetextFilterBox.removeEventListener("command", this.requestsFreetextFilterEvent, false);
+    this.userInputTimer.cancel();
     $("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
     $("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
     $("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
     $("#request-menu-context-copy-image-as-data-uri").removeEventListener("command", this._onContextCopyImageAsDataUriCommand, false);
     $("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
     $("#request-menu-context-perf").removeEventListener("command", this._onContextPerfCommand, false);
 
     $("#requests-menu-reload-notice-button").removeEventListener("command", this._onReloadCommand, false);
@@ -666,16 +678,41 @@ RequestsMenuView.prototype = Heritage.ex
     } else {
       requestTextarea.value = null;
       responseTextare.value = null;
       $("#raw-headers").hidden = true;
     }
   },
 
   /**
+   * Handles the timeout on the freetext filter textbox
+   */
+  requestsFreetextFilterEvent: function() {
+    this.userInputTimer.cancel();
+    this._currentFreetextFilter = this.freetextFilterBox.value || "";
+
+    if (this._currentFreetextFilter.length === 0) {
+      this.freetextFilterBox.removeAttribute("filled");
+    } else {
+      this.freetextFilterBox.setAttribute("filled", true);
+    }
+
+    this.userInputTimer.initWithCallback(this.reFilterRequests, FREETEXT_FILTER_SEARCH_DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
+  },
+
+  /**
+   * Refreshes the view contents with the newly selected filters
+   */
+  reFilterRequests: function() {
+    this.filterContents(this._filterPredicate);
+    this.refreshSummary();
+    this.refreshZebra();
+  },
+
+  /**
    * Filters all network requests in this container by a specified type.
    *
    * @param string aType
    *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
    *        "flash" or "other".
    */
   filterOn: function(aType = "all") {
     if (aType === "all") {
@@ -695,19 +732,17 @@ RequestsMenuView.prototype = Heritage.ex
     }
     else if (this._activeFilters.indexOf(aType) === -1) {
       this._enableFilter(aType);
     }
     else {
       this._disableFilter(aType);
     }
 
-    this.filterContents(this._filterPredicate);
-    this.refreshSummary();
-    this.refreshZebra();
+    this.reFilterRequests();
   },
 
   /**
    * Same as `filterOn`, except that it only allows a single type exclusively.
    *
    * @param string aType
    *        @see RequestsMenuView.prototype.fitlerOn
    */
@@ -766,44 +801,41 @@ RequestsMenuView.prototype = Heritage.ex
   },
 
   /**
    * Returns a predicate that can be used to test if a request matches any of
    * the active filters.
    */
   get _filterPredicate() {
     let filterPredicates = this._allFilterPredicates;
-
-     if (this._activeFilters.length === 1) {
-       // The simplest case: only one filter active.
-       return filterPredicates[this._activeFilters[0]].bind(this);
-     } else {
-       // Multiple filters active.
-       return requestItem => {
-         return this._activeFilters.some(filterName => {
-           return filterPredicates[filterName].call(this, requestItem);
-         });
-       };
-     }
+    let currentFreetextFilter = this._currentFreetextFilter;
+
+    return requestItem => {
+      return this._activeFilters.some(filterName => {
+        return filterPredicates[filterName].call(this, requestItem) &&
+                filterPredicates["freetext"].call(this, requestItem, currentFreetextFilter);
+      });
+    };
   },
 
   /**
    * Returns an object with all the filter predicates as [key: function] pairs.
    */
   get _allFilterPredicates() ({
     all: () => true,
     html: this.isHtml,
     css: this.isCss,
     js: this.isJs,
     xhr: this.isXHR,
     fonts: this.isFont,
     images: this.isImage,
     media: this.isMedia,
     flash: this.isFlash,
-    other: this.isOther
+    other: this.isOther,
+    freetext: this.isFreetextMatch
   }),
 
   /**
    * Sorts all network requests in this container by a specified detail.
    *
    * @param string aType
    *        Either "status", "method", "file", "domain", "type", "transferred",
    *        "size" or "waterfall".
@@ -952,16 +984,19 @@ RequestsMenuView.prototype = Heritage.ex
       mimeType.contains("/x-shockwave-flash"))) ||
     url.contains(".swf") ||
     url.contains(".flv"),
 
   isOther: function(e)
     !this.isHtml(e) && !this.isCss(e) && !this.isJs(e) && !this.isXHR(e) &&
     !this.isFont(e) && !this.isImage(e) && !this.isMedia(e) && !this.isFlash(e),
 
+  isFreetextMatch: function({ attachment: { url } }, text) //no text is a positive match
+    !text || url.contains(text),
+
   /**
    * Predicates used when sorting items.
    *
    * @param object aFirst
    *        The first item used in the comparison.
    * @param object aSecond
    *        The second item used in the comparison.
    * @return number
@@ -1860,17 +1895,18 @@ RequestsMenuView.prototype = Heritage.ex
   _canvas: null,
   _ctx: null,
   _cachedWaterfallWidth: 0,
   _firstRequestStartedMillis: -1,
   _lastRequestEndedMillis: -1,
   _updateQueue: [],
   _updateTimeout: null,
   _resizeTimeout: null,
-  _activeFilters: ["all"]
+  _activeFilters: ["all"],
+  _currentFreetextFilter: ""
 });
 
 /**
  * Functions handling the sidebar details view.
  */
 function SidebarView() {
   dumpn("SidebarView was instantiated");
 }
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -42,16 +42,28 @@
                 accesskey="&netmonitorUI.summary.editAndResend.accesskey;"/>
       <menuseparator id="request-menu-context-separator"/>
       <menuitem id="request-menu-context-perf"
                 label="&netmonitorUI.context.perfTools;"
                 accesskey="&netmonitorUI.context.perfTools.accesskey;"/>
     </menupopup>
   </popupset>
 
+  <commandset>
+    <command id="freeTextFilterCommand"
+             oncommand="NetMonitorView.RequestsMenu.freetextFilterBox.focus()"/>
+  </commandset>
+
+  <keyset>
+    <key id="freeTextFilterKey"
+         key="&netmonitorUI.footer.filterFreetext.key;"
+         modifiers="accel"
+         command="freeTextFilterCommand"/>
+  </keyset>
+
   <deck id="body" class="theme-sidebar" flex="1">
 
     <vbox id="network-inspector-view" flex="1">
       <hbox id="network-table-and-sidebar"
             class="devtools-responsive-container"
             flex="1">
         <vbox id="network-table" flex="1" class="devtools-main-content">
           <toolbar id="requests-menu-toolbar"
@@ -747,16 +759,24 @@
                 data-key="flash"
                 label="&netmonitorUI.footer.filterFlash;">
         </button>
         <button id="requests-menu-filter-other-button"
                 class="requests-menu-filter-button requests-menu-footer-button"
                 data-key="other"
                 label="&netmonitorUI.footer.filterOther;">
         </button>
+        <spacer id="requests-menu-spacer-textbox"
+                class="requests-menu-footer-spacer"
+                flex="0"/>
+        <textbox id="requests-menu-filter-freetext-text"
+                 class="requests-menu-footer-textbox devtools-searchinput"
+                 type="search"
+                 required="true"
+                 placeholder="&netmonitorUI.footer.filterFreetext.label;"/>
         <spacer id="requests-menu-spacer"
                 class="requests-menu-footer-spacer"
                 flex="100"/>
         <button id="requests-menu-network-summary-button"
                 class="requests-menu-footer-button"
                 tooltiptext="&netmonitorUI.footer.perf;"/>
         <label id="requests-menu-network-summary-label"
                class="plain requests-menu-footer-label"
--- a/browser/devtools/netmonitor/test/browser_net_filter-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-01.js
@@ -1,33 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test if filtering items in the network table works correctly.
  */
 const BASIC_REQUESTS = [
-  { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
-  { url: "sjs_content-type-test-server.sjs?fmt=css" },
-  { url: "sjs_content-type-test-server.sjs?fmt=js" },
+  { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined&text=sample" },
+  { url: "sjs_content-type-test-server.sjs?fmt=css&text=sample" },
+  { url: "sjs_content-type-test-server.sjs?fmt=js&text=sample" },
 ];
 
 const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
   { url: "sjs_content-type-test-server.sjs?fmt=font" },
   { url: "sjs_content-type-test-server.sjs?fmt=image" },
   { url: "sjs_content-type-test-server.sjs?fmt=audio" },
   { url: "sjs_content-type-test-server.sjs?fmt=video" },
 ]);
 
 const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
   { url: "sjs_content-type-test-server.sjs?fmt=flash" },
 ]);
 
 function test() {
   initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
+
+    function setFreetextFilter(value) {
+      // Set the text and manually call all callbacks synchronously to avoid the timeout
+      RequestsMenu.freetextFilterBox.value = value;
+      RequestsMenu.requestsFreetextFilterEvent();
+      RequestsMenu.userInputTimer.cancel();
+      RequestsMenu.reFilterRequests();
+    }
+
     info("Starting test... ");
 
     let { $, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 8).then(() => {
@@ -91,26 +100,47 @@ function test() {
           testFilterButtons(aMonitor, "flash");
           return testContents([0, 0, 0, 0, 0, 0, 0, 1]);
         })
         .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
           testFilterButtons(aMonitor, "all");
           return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
         })
+        .then(() => {
+          // Text in filter box that matches nothing should hide all.
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+          setFreetextFilter("foobar");
+          return testContents([0, 0, 0, 0, 0, 0, 0, 0]);
+        })
+        .then(() => {
+          // Text in filter box that matches should filter out everything else.
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+          setFreetextFilter("sample");
+          return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
+        })
         // ...then combine multiple filters together.
         .then(() => {
           // Enable filtering for html and css; should show request of both type.
+          setFreetextFilter("");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
           testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
           return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
         })
         .then(() => {
+          // Html and css filter enabled and text filter should show just the html and css match.
+          // Should not show both the items that match the button plus the items that match the text.
+          setFreetextFilter("sample");
+          return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
+        })
+
+        .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
+          setFreetextFilter("");
           testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
           return testContents([1, 1, 0, 0, 0, 0, 0, 1]);
         })
         .then(() => {
           // Disable some filters. Only one left active.
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
           testFilterButtons(aMonitor, "html");
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -359,16 +359,21 @@ StyleSheetEditor.prototype = {
    * Create source editor and load state into it.
    * @param  {DOMElement} inputElement
    *         Element to load source editor in
    *
    * @return {Promise}
    *         Promise that will resolve when the style editor is loaded.
    */
   load: function(inputElement) {
+    if (this._isDestroyed) {
+      return promise.reject("Won't load source editor as the style sheet has " +
+                            "already been removed from Style Editor.");
+    }
+
     this._inputElement = inputElement;
 
     let config = {
       value: this._state.text,
       lineNumbers: true,
       mode: Editor.modes.css,
       readOnly: false,
       autoCloseBrackets: "{}()[]",
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -3233,17 +3233,17 @@ Widgets.ObjectRenderers.add({
 /**
  * The widget user for displaying Promise objects.
  */
 Widgets.ObjectRenderers.add({
   byClass: "Promise",
 
   render: function()
   {
-    let { ownProperties, safeGetterValues } = this.objectActor.preview;
+    let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
     if ((!ownProperties && !safeGetterValues) || this.options.concise) {
       this._renderConciseObject();
       return;
     }
 
     this._renderObjectPrefix();
     let container = this.element;
     let addedPromiseInternalProps = false;
@@ -3269,17 +3269,17 @@ Widgets.ObjectRenderers.add({
 /**
  * The widget used for displaying generic JS object previews.
  */
 Widgets.ObjectRenderers.add({
   byKind: "Object",
 
   render: function()
   {
-    let { ownProperties, safeGetterValues } = this.objectActor.preview;
+    let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
     if ((!ownProperties && !safeGetterValues) || this.options.concise) {
       this._renderConciseObject();
       return;
     }
 
     this._renderObjectPrefix();
     this._renderObjectProperties(this.element, false);
     this._renderObjectSuffix();
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -330,16 +330,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
 [browser_webconsole_live_filtering_of_message_types.js]
 [browser_webconsole_live_filtering_on_search_strings.js]
 [browser_webconsole_message_node_id.js]
 [browser_webconsole_netlogging.js]
 [browser_webconsole_network_panel.js]
 [browser_webconsole_notifications.js]
 [browser_webconsole_open-links-without-callback.js]
+[browser_webconsole_promise.js]
 [browser_webconsole_output_copy_newlines.js]
 [browser_webconsole_output_order.js]
 [browser_webconsole_property_provider.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_split.js]
 [browser_webconsole_split_escape_key.js]
 [browser_webconsole_split_focus.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_promise.js
@@ -0,0 +1,51 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 1148759 - Test the webconsole can display promises inside objects.
+
+const TEST_URI = "data:text/html;charset=utf8,test for console and promises";
+
+let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+
+let LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
+let LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH;
+DebuggerServer.LONG_STRING_LENGTH = 100;
+DebuggerServer.LONG_STRING_INITIAL_LENGTH = 50;
+
+let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
+let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+let inputTests = [
+  // 0
+  {
+    input: "({ x: Promise.resolve() })",
+    output: "Object { x: Promise }",
+    printOutput: "[object Object]",
+    inspectable: true,
+    variablesViewLabel: "Object"
+  },
+];
+
+longString = initialString = null;
+
+function test() {
+  requestLongerTimeout(2);
+
+  registerCleanupFunction(() => {
+    DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
+    DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
+  });
+
+  Task.spawn(function*() {
+    let {tab} = yield loadTab(TEST_URI);
+    let hud = yield openConsole(tab);
+    return checkOutputForInputs(hud, inputTests);
+  }).then(finishUp);
+}
+
+function finishUp() {
+  longString = initialString = inputTests = null;
+  finishTest();
+}
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -524,19 +524,22 @@ WiFiRuntime.prototype = {
       },
       onCloseWindow() {},
       onWindowTitleChange() {}
     };
     Services.wm.addListener(windowListener);
 
     // |openDialog| is typically a blocking API, so |executeSoon| to get around this
     DevToolsUtils.executeSoon(() => {
+      // Height determines the size of the QR code.  Force a minimum size to
+      // improve scanability.
+      const MIN_HEIGHT = 600;
       let win = Services.wm.getMostRecentWindow("devtools:webide");
       let width = win.outerWidth * 0.8;
-      let height = win.outerHeight * 0.5;
+      let height = Math.max(win.outerHeight * 0.5, MIN_HEIGHT);
       win.openDialog("chrome://webide/content/wifi-auth.xhtml",
                      WINDOW_ID,
                      "modal=yes,width=" + width + ",height=" + height, session);
     });
 
     return {
       close() {
         if (!promptWindow) {
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.dtd
@@ -119,16 +119,21 @@
 <!-- LOCALIZATION NOTE (netmonitorUI.footer.filterFlash): This is the label displayed
   -  in the network details footer for the "Flash" filtering button. -->
 <!ENTITY netmonitorUI.footer.filterFlash  "Flash">
 
 <!-- LOCALIZATION NOTE (netmonitorUI.footer.filterOther): This is the label displayed
   -  in the network details footer for the "Other" filtering button. -->
 <!ENTITY netmonitorUI.footer.filterOther  "Other">
 
+<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterFreetext): This is the label displayed
+  -  in the network details footer for the url filtering textbox. -->
+<!ENTITY netmonitorUI.footer.filterFreetext.label  "Filter URLs">
+<!ENTITY netmonitorUI.footer.filterFreetext.key  "F">
+
 <!-- LOCALIZATION NOTE (netmonitorUI.footer.clear): This is the label displayed
   -  in the network details footer for the "Clear" button. -->
 <!ENTITY netmonitorUI.footer.clear  "Clear">
 
 <!-- LOCALIZATION NOTE (netmonitorUI.footer.clear): This is the label displayed
   -  in the network details footer for the performance analysis button. -->
 <!ENTITY netmonitorUI.footer.perf   "Toggle performance analysis…">
 
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,55 @@ 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);
+    global.addMessageListener("BrowserPlugins:Test:ClearCrashData", 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 +90,34 @@ 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;
+      case "BrowserPlugins:Test:ClearCrashData":
+        // This message should ONLY ever be sent by automated tests.
+        if (Services.prefs.getBoolPref("plugins.testmode")) {
+          this.pluginCrashData.clear();
+        }
     }
   },
 
   onPageShow: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
@@ -92,17 +132,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 +343,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 +374,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 +555,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 +887,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',
deleted file mode 100644
--- a/browser/themes/linux/devtools/commandline.css
+++ /dev/null
@@ -1,154 +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/. */
-
-.gcli-body {
-  margin: 0;
-  font: message-box;
-  color: hsl(210,30%,85%);
-}
-
-#gcli-output-root,
-#gcli-tooltip-root {
-  border: 1px solid hsl(206,37%,4%);
-  box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
-  background-image: linear-gradient(hsla(209,18%,18%,0.9), hsl(209,23%,18%));
-  border-radius: 3px;
-}
-
-#gcli-output-root {
-  padding: 5px 10px;
-  border-bottom-left-radius: 0;
-  border-bottom-right-radius: 0;
-  border-bottom: 0;
-}
-
-#gcli-tooltip-root {
-  padding: 5px 0px;
-}
-
-#gcli-tooltip-connector {
-  margin-top: -1px;
-  margin-left: 8px;
-  width: 20px;
-  height: 10px;
-  border-left: 1px solid hsl(206,37%,4%);
-  border-right: 1px solid hsl(206,37%,4%);
-  background-color: hsl(209,23%,18%);
-}
-
-.gcli-tt-description,
-.gcli-tt-error {
-  padding: 0 10px;
-}
-
-.gcli-row-out {
-  padding: 0 5px;
-  line-height: 1.2em;
-  border-top: none;
-  border-bottom: none;
-  color: hsl(210,30%,85%);
-}
-
-.gcli-row-out p,
-.gcli-row-out h1,
-.gcli-row-out h2,
-.gcli-row-out h3 {
-  margin: 5px 0;
-}
-
-.gcli-row-out h1,
-.gcli-row-out h2,
-.gcli-row-out h3,
-.gcli-row-out h4,
-.gcli-row-out h5,
-.gcli-row-out th,
-.gcli-row-out strong,
-.gcli-row-out pre {
-  color: hsl(210,30%,95%);
-}
-
-.gcli-row-out pre {
-  font-size: 80%;
-}
-
-.gcli-row-out td {
-  white-space: nowrap;
-}
-
-.gcli-out-shortcut,
-.gcli-help-synopsis {
-  padding: 0 3px;
-  margin: 0 4px;
-  font-weight: normal;
-  font-size: 90%;
-  border-radius: 3px;
-  background-color: hsl(209,23%,18%);
-  border: 1px solid hsl(206,37%,4%);
-}
-
-.gcli-out-shortcut:before,
-.gcli-help-synopsis:before {
-  color: hsl(210,30%,85%);
-  -moz-padding-end: 2px;
-}
-
-.gcli-help-arrow {
-  color: #666;
-}
-
-.gcli-help-description {
-  margin: 0 20px;
-  padding: 0;
-}
-
-.gcli-help-parameter {
-  margin: 0 30px;
-  padding: 0;
-}
-
-.gcli-help-header {
-  margin: 10px 0 6px;
-}
-
-.gcli-menu-name {
-  -moz-padding-start: 8px;
-}
-
-.gcli-menu-desc {
-  -moz-padding-end: 8px;
-  color: hsl(210,30%,75%);
-}
-
-.gcli-menu-name:hover,
-.gcli-menu-desc:hover {
-  background-color: hsla(0,0%,0%,.3);
-}
-
-.gcli-menu-highlight,
-.gcli-menu-highlight:hover {
-  background-color: hsla(0,100%,100%,.1);
-}
-
-.gcli-menu-typed {
-  color: hsl(25,78%,50%);
-}
-
-.gcli-menu-more {
-  font-size: 80%;
-  text-align: end;
-  -moz-padding-end: 8px;
-}
-
-.gcli-addon-disabled {
-  opacity: 0.6;
-  text-decoration: line-through;
-}
-
-.gcli-breakpoint-label {
-  font-weight: bold;
-}
-
-.gcli-breakpoint-lineText {
-  font-family: monospace;
-}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -264,17 +264,17 @@ browser.jar:
   skin/classic/browser/devtools/command-rulers@2x.png         (../shared/devtools/images/command-rulers@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css          (../shared/devtools/ruleview.css)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (../shared/devtools/images/webconsole.png)
   skin/classic/browser/devtools/webconsole@2x.png               (../shared/devtools/images/webconsole@2x.png)
-  skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
+  skin/classic/browser/devtools/commandline.css              (../shared/devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png  (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
   skin/classic/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
deleted file mode 100644
--- a/browser/themes/osx/devtools/commandline.css
+++ /dev/null
@@ -1,154 +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/. */
-
-.gcli-body {
-  margin: 0;
-  font: message-box;
-  color: hsl(210,30%,85%);
-}
-
-#gcli-output-root,
-#gcli-tooltip-root {
-  border: 1px solid hsl(206,37%,4%);
-  box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
-  background-image: linear-gradient(hsla(209,18%,18%,0.9), hsl(209,23%,18%));
-  border-radius: 3px;
-}
-
-#gcli-output-root {
-  padding: 5px 10px;
-  border-bottom-left-radius: 0;
-  border-bottom-right-radius: 0;
-  border-bottom: 0;
-}
-
-#gcli-tooltip-root {
-  padding: 5px 0px;
-}
-
-#gcli-tooltip-connector {
-  margin-top: -1px;
-  margin-left: 8px;
-  width: 20px;
-  height: 10px;
-  border-left: 1px solid hsl(206,37%,4%);
-  border-right: 1px solid hsl(206,37%,4%);
-  background-color: hsl(209,23%,18%);
-}
-
-.gcli-tt-description,
-.gcli-tt-error {
-  padding: 0 10px;
-}
-
-.gcli-row-out {
-  padding: 0 5px;
-  line-height: 1.2em;
-  border-top: none;
-  border-bottom: none;
-  color: hsl(210,30%,85%);
-}
-
-.gcli-row-out p,
-.gcli-row-out h1,
-.gcli-row-out h2,
-.gcli-row-out h3 {
-  margin: 5px 0;
-}
-
-.gcli-row-out h1,
-.gcli-row-out h2,
-.gcli-row-out h3,
-.gcli-row-out h4,
-.gcli-row-out h5,
-.gcli-row-out th,
-.gcli-row-out strong,
-.gcli-row-out pre {
-  color: hsl(210,30%,95%);
-}
-
-.gcli-row-out pre {
-  font-size: 80%;
-}
-
-.gcli-row-out td {
-  white-space: nowrap;
-}
-
-.gcli-out-shortcut,
-.gcli-help-synopsis {
-  padding: 0 3px;
-  margin: 0 4px;
-  font-weight: normal;
-  font-size: 90%;
-  border-radius: 3px;
-  background-color: hsl(209,23%,18%);
-  border: 1px solid hsl(206,37%,4%);
-}
-
-.gcli-out-shortcut:before,
-.gcli-help-synopsis:before {
-  color: hsl(210,30%,85%);
-  -moz-padding-end: 2px;
-}
-
-.gcli-help-arrow {
-  color: #666;
-}
-
-.gcli-help-description {
-  margin: 0 20px;
-  padding: 0;
-}
-
-.gcli-help-parameter {
-  margin: 0 30px;
-  padding: 0;
-}
-
-.gcli-help-header {
-  margin: 10px 0 6px;
-}
-
-.gcli-menu-name {
-  -moz-padding-start: 8px;
-}
-
-.gcli-menu-desc {
-  -moz-padding-end: 8px;
-  color: hsl(210,30%,75%);
-}
-
-.gcli-menu-name:hover,
-.gcli-menu-desc:hover {
-  background-color: hsla(0,0%,0%,.3);
-}
-
-.gcli-menu-highlight,
-.gcli-menu-highlight:hover {
-  background-color: hsla(0,100%,100%,.1);
-}
-
-.gcli-menu-typed {
-  color: hsl(25,78%,50%);
-}
-
-.gcli-menu-more {
-  font-size: 80%;
-  text-align: end;
-  -moz-padding-end: 8px;
-}
-
-.gcli-addon-disabled {
-  opacity: 0.6;
-  text-decoration: line-through;
-}
-
-.gcli-breakpoint-label {
-  font-weight: bold;
-}
-
-.gcli-breakpoint-lineText {
-  font-family: monospace;
-}
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -390,17 +390,17 @@ browser.jar:
   skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
   skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
   skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/command-rulers.png            (../shared/devtools/images/command-rulers.png)
   skin/classic/browser/devtools/command-rulers@2x.png         (../shared/devtools/images/command-rulers@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
   skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 * skin/classic/browser/devtools/ruleview.css                (../shared/devtools/ruleview.css)
-  skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
+  skin/classic/browser/devtools/commandline.css             (../shared/devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (../shared/devtools/images/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (../shared/devtools/images/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-breakpoint@2x.png        (../shared/devtools/images/editor-breakpoint@2x.png)
   skin/classic/browser/devtools/editor-debug-location.png    (../shared/devtools/images/editor-debug-location.png)
   skin/classic/browser/devtools/editor-debug-location@2x.png    (../shared/devtools/images/editor-debug-location@2x.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
rename from browser/themes/windows/devtools/commandline.css
rename to browser/themes/shared/devtools/commandline.css
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -692,16 +692,38 @@ label.requests-menu-status-code {
   color: rgba(245,247,250,1); /* Light foreground text */
 }
 
 .requests-menu-footer-label {
   padding-top: 3px;
   font-weight: 600;
 }
 
+#requests-menu-filter-freetext-text {
+  transition-property: max-width, -moz-padding-end, -moz-padding-start;
+  transition-duration: 250ms;
+  transition-timing-function: ease;
+}
+
+#requests-menu-filter-freetext-text:not([focused]):not([filled]) > .textbox-input-box {
+  overflow: hidden;
+}
+
+#requests-menu-filter-freetext-text:not([focused]):not([filled]) {
+  max-width: 20px !important;
+  -moz-padding-end: 5px;
+  -moz-padding-start: 22px;
+  background-position: 8px center, top left, top left;
+}
+
+#requests-menu-filter-freetext-text[focused],
+#requests-menu-filter-freetext-text[filled] {
+  max-width: 200px !important;
+}
+
 /* Performance analysis buttons */
 
 #requests-menu-network-summary-button {
   background: none;
   box-shadow: none;
   border-color: transparent;
   list-style-image: url(profiler-stopwatch.svg);
   -moz-padding-end: 0;
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -251,16 +251,19 @@ description > html|a {
 
 #tabsElement {
   -moz-margin-end: 4px; /* add the 4px end-margin of other elements */
 }
 
 #telemetryLearnMore,
 #FHRLearnMore,
 #crashReporterLearnMore {
+  /* provide some margin between the links and the label text */
+  /* !important is needed to override the rules defined in common.css */
+  -moz-margin-start: 20px !important;
   /* center the links */
   margin-top: 8px;
   margin-bottom: 8px;
 }
 
 .indent {
   -moz-margin-start: 33px;
 }
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -321,17 +321,17 @@ browser.jar:
         skin/classic/browser/devtools/filetype-dir-open.svg         (../shared/devtools/images/filetypes/dir-open.svg)
         skin/classic/browser/devtools/filetype-globe.svg            (../shared/devtools/images/filetypes/globe.svg)
         skin/classic/browser/devtools/filetype-store.svg            (../shared/devtools/images/filetypes/store.svg)
         skin/classic/browser/devtools/commandline-icon.png          (../shared/devtools/images/commandline-icon.png)
         skin/classic/browser/devtools/commandline-icon@2x.png          (../shared/devtools/images/commandline-icon@2x.png)
         skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
         skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
 *       skin/classic/browser/devtools/ruleview.css                  (../shared/devtools/ruleview.css)
-        skin/classic/browser/devtools/commandline.css               (devtools/commandline.css)
+        skin/classic/browser/devtools/commandline.css               (../shared/devtools/commandline.css)
         skin/classic/browser/devtools/command-paintflashing.png     (../shared/devtools/images/command-paintflashing.png)
         skin/classic/browser/devtools/command-paintflashing@2x.png  (../shared/devtools/images/command-paintflashing@2x.png)
         skin/classic/browser/devtools/command-screenshot.png        (../shared/devtools/images/command-screenshot.png)
         skin/classic/browser/devtools/command-screenshot@2x.png     (../shared/devtools/images/command-screenshot@2x.png)
         skin/classic/browser/devtools/command-responsivemode.png    (../shared/devtools/images/command-responsivemode.png)
         skin/classic/browser/devtools/command-responsivemode@2x.png (../shared/devtools/images/command-responsivemode@2x.png)
         skin/classic/browser/devtools/command-scratchpad.png        (../shared/devtools/images/command-scratchpad.png)
         skin/classic/browser/devtools/command-scratchpad@2x.png     (../shared/devtools/images/command-scratchpad@2x.png)
--- a/mobile/android/base/tabqueue/TabQueueDispatcher.java
+++ b/mobile/android/base/tabqueue/TabQueueDispatcher.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.tabqueue;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -25,16 +26,18 @@ import android.util.Log;
  */
 public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
     private static final String LOGTAG = "Gecko" + TabQueueDispatcher.class.getSimpleName();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        GeckoAppShell.ensureCrashHandling();
+
         Intent intent = getIntent();
 
         // For the moment lets exit early and start fennec as normal if we're not in nightly with
         // the tab queue build flag.
         if (!AppConstants.MOZ_ANDROID_TAB_QUEUE) {
             loadNormally(intent);
             finish();
         }
--- a/mobile/android/chrome/content/aboutPasswords.js
+++ b/mobile/android/chrome/content/aboutPasswords.js
@@ -30,18 +30,22 @@ function copyStringAndToast(string, noti
     clipboard.copyString(string);
     gChromeWin.NativeWindow.toast.show(notifyString, "short");
   } catch (e) {
     debug("Error copying from about:passwords");
     gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("passwordsDetails.copyFailed"), "short");
   }
 }
 
+// Delay filtering while typing in MS
+const FILTER_DELAY = 500;
+
 let Passwords = {
   _logins: [],
+  _filterTimer: null,
 
   _getLogins: function() {
     let logins;
     try {
       logins = Services.logins.getAllLogins();
     } catch(e) {
       // Master password was not entered
       debug("Master password permissions error: " + e);
@@ -61,27 +65,45 @@ let Passwords = {
 
     document.getElementById("copyusername-btn").addEventListener("click", this._copyUsername.bind(this), false);
     document.getElementById("copypassword-btn").addEventListener("click", this._copyPassword.bind(this), false);
     document.getElementById("details-header").addEventListener("click", this._openLink.bind(this), false);
 
     let filterInput = document.getElementById("filter-input");
     let filterContainer = document.getElementById("filter-input-container");
 
-    filterInput.addEventListener("input", this._filter.bind(this), false);
+    filterInput.addEventListener("input", (event) => {
+      // Stop any in-progress filter timer
+      if (this._filterTimer) {
+        clearTimeout(this._filterTimer);
+        this._filterTimer = null;
+      }
+
+      // Start a new timer
+      this._filterTimer = setTimeout(() => {
+        this._filter(event);
+      }, FILTER_DELAY);
+    }, false);
+
     filterInput.addEventListener("blur", (event) => {
       filterContainer.setAttribute("hidden", true);
     });
 
     document.getElementById("filter-button").addEventListener("click", (event) => {
       filterContainer.removeAttribute("hidden");
       filterInput.focus();
     }, false);
 
     document.getElementById("filter-clear").addEventListener("click", (event) => {
+      // Stop any in-progress filter timer
+      if (this._filterTimer) {
+        clearTimeout(this._filterTimer);
+        this._filterTimer = null;
+      }
+
       filterInput.blur();
       filterInput.value = "";
       this._loadList(this._logins);
     }, false);
 
     this._showList();
   },
 
@@ -118,65 +140,74 @@ let Passwords = {
     } else {
       // Clear any previous detail addon
       let detailItem = document.querySelector("#login-details > .login-item");
       detailItem.login = null;
       this._showList();
     }
   },
 
+  _onLoginClick: function (event) {
+    let loginItem = event.currentTarget;
+    let login = loginItem.login;
+    if (!login) {
+      debug("No login!");
+      return;
+    }
+
+    let prompt = new Prompt({
+      window: window,
+    });
+    let menuItems = [
+      { label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") },
+      { label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") },
+      { label: gStringBundle.GetStringFromName("passwordsMenu.details") },
+      { label: gStringBundle.GetStringFromName("passwordsMenu.delete") }
+    ];
+
+    prompt.setSingleChoiceItems(menuItems);
+    prompt.show((data) => {
+      // Switch on indices of buttons, as they were added when creating login item.
+      switch (data.button) {
+        case 0:
+          copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
+          break;
+        case 1:
+          copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
+          break;
+        case 2:
+          this._showDetails(loginItem);
+          history.pushState({ id: login.guid }, document.title);
+          break;
+        case 3:
+          let confirmPrompt = new Prompt({
+            window: window,
+            message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"),
+            buttons: [
+              gStringBundle.GetStringFromName("passwordsDialog.confirm"),
+              gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
+          });
+          confirmPrompt.show((data) => {
+            switch (data.button) {
+              case 0:
+                // Corresponds to "confirm" button.
+                Services.logins.removeLogin(login);
+            }
+          });
+      }
+    });
+  },
+
   _createItemForLogin: function (login) {
     let loginItem = document.createElement("div");
 
     loginItem.setAttribute("loginID", login.guid);
     loginItem.className = "login-item list-item";
 
-    loginItem.addEventListener("click", () => {
-      let prompt = new Prompt({
-        window: window,
-      });
-      let menuItems = [
-        { label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") },
-        { label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") },
-        { label: gStringBundle.GetStringFromName("passwordsMenu.details") },
-        { label: gStringBundle.GetStringFromName("passwordsMenu.delete") } ];
-
-      prompt.setSingleChoiceItems(menuItems);
-      prompt.show((data) => {
-        // Switch on indices of buttons, as they were added when creating login item.
-        switch (data.button) {
-          case 0:
-            copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
-            break;
-          case 1:
-            copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
-            break;
-          case 2:
-            this._showDetails(loginItem);
-            history.pushState({ id: login.guid }, document.title);
-            break;
-          case 3:
-            let confirmPrompt = new Prompt({
-              window: window,
-              message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"),
-              buttons: [
-                gStringBundle.GetStringFromName("passwordsDialog.confirm"),
-                gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
-            });
-            confirmPrompt.show((data) => {
-              switch (data.button) {
-                case 0:
-                  // Corresponds to "confirm" button.
-                  Services.logins.removeLogin(login);
-              }
-            });
-        }
-      });
-
-    }, true);
+    loginItem.addEventListener("click", this, true);
 
     // Create item icon.
     let img = document.createElement("div");
     img.className = "icon";
 
     // Load favicon from cache.
     Messaging.sendRequestForResult({
       type: "Favicon:CacheLoad",
@@ -225,16 +256,20 @@ let Passwords = {
   },
 
   handleEvent: function (event) {
     switch (event.type) {
       case "popstate": {
         this._onPopState(event);
         break;
       }
+      case "click": {
+        this._onLoginClick(event);
+        break;
+      }
     }
   },
 
   observe: function (subject, topic, data) {
     switch(topic) {
       case "passwordmgr-storage-changed": {
         // Reload passwords content.
         this._loadList(this._getLogins());
--- a/testing/marionette/client/marionette/tests/unit/test_window_handles.py
+++ b/testing/marionette/client/marionette/tests/unit/test_window_handles.py
@@ -4,40 +4,36 @@
 
 from marionette import MarionetteTestCase
 from marionette_driver.keys import Keys
 
 
 class TestWindowHandles(MarionetteTestCase):
 
     def test_new_tab_window_handles(self):
-        keys = [Keys.SHIFT]
+
+        keys = []
         if self.marionette.session_capabilities['platformName'] == 'DARWIN':
             keys.append(Keys.META)
         else:
             keys.append(Keys.CONTROL)
-        keys.append('a')
-
-        # Put some history in the tab so this results in a fresh tab opening.
-        self.marionette.navigate("about:blank")
-        self.marionette.navigate("data:text/html, <div>Text</div>")
-        self.marionette.navigate("about:blank")
+        keys.append('t')
 
         origin_win = self.marionette.current_window_handle
 
         with self.marionette.using_context("chrome"):
             main_win = self.marionette.find_element("id", "main-window")
             main_win.send_keys(*keys)
 
         self.wait_for_condition(lambda mn: len(mn.window_handles) == 2)
         handles = self.marionette.window_handles
         handles.remove(origin_win)
-        addons_page = handles.pop()
-        self.marionette.switch_to_window(addons_page)
-        self.assertEqual(self.marionette.get_url(), "about:addons")
+        new_tab = handles.pop()
+        self.marionette.switch_to_window(new_tab)
+        self.assertEqual(self.marionette.get_url(), "about:newtab")
         self.marionette.close()
 
         self.marionette.switch_to_window(origin_win)
         self.assertEqual(self.marionette.get_url(), "about:blank")
 
     def test_link_opened_tab_window_handles(self):
         tab_testpage = self.marionette.absolute_url("windowHandles.html")
         self.marionette.navigate(tab_testpage)
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -107,30 +107,38 @@ this.BrowserTestUtils = {
     });
   },
 
   /**
    * @param {Object} options
    *        {
    *          private: A boolean indicating if the window should be
    *                   private
+   *          remote:  A boolean indicating if the window should run
+   *                   remote browser tabs or not. If omitted, the window
+   *                   will choose the profile default state.
    *        }
    * @return {Promise}
    *         Resolves with the new window once it is loaded.
    */
-  openNewBrowserWindow(options) {
+  openNewBrowserWindow(options={}) {
     let argString = Cc["@mozilla.org/supports-string;1"].
                     createInstance(Ci.nsISupportsString);
     argString.data = "";
     let features = "chrome,dialog=no,all";
 
-    if (options && options.private || false) {
+    if (options.private) {
       features += ",private";
     }
 
+    if (options.hasOwnProperty("remote")) {
+      let remoteState = options.remote ? "remote" : "non-remote";
+      features += `,${remoteState}`;
+    }
+
     let win = Services.ww.openWindow(
       null, Services.prefs.getCharPref("browser.chromeURL"), "_blank",
       features, argString);
 
     // Wait for browser-delayed-startup-finished notification, it indicates
     // that the window has loaded completely and is ready to be used for
     // testing.
     return TestUtils.topicObserved("browser-delayed-startup-finished",
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -640,20 +640,25 @@ this.DownloadIntegration = {
         // is due to the fact that "deleteTempFileOnExit" is false on Mac, where
         // downloads to be opened with external applications are preserved in
         // the "Downloads" folder like normal downloads.
         let isTemporaryDownload =
           aDownload.launchWhenSucceeded && (aDownload.source.isPrivate ||
           Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
         // Permanently downloaded files are made accessible by other users on
         // this system, while temporary downloads are marked as read-only.
-        let unixMode = isTemporaryDownload ? 0o400 : 0o666;
-        // On Unix, the umask of the process is respected.  This call has no
-        // effect on Windows.
-        yield OS.File.setPermissions(aDownload.target.path, { unixMode });
+        let options = {};
+        if (isTemporaryDownload) {
+          options.unixMode = 0o400;
+          options.winAttributes = {readOnly: true};
+        } else {
+          options.unixMode = 0o666;
+        }
+        // On Unix, the umask of the process is respected.
+        yield OS.File.setPermissions(aDownload.target.path, options);
       } catch (ex) {
         // We should report errors with making the permissions less restrictive
         // or marking the file as read-only on Unix and Mac, but this should not
         // prevent the download from completing.
         // The setPermissions API error EPERM is expected to occur when working
         // on a file system that does not support file permissions, like FAT32,
         // thus we don't report this error.
         if (!(ex instanceof OS.File.Error) || ex.unixErrno != OS.Constants.libc.EPERM) {
--- a/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
+++ b/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
@@ -139,35 +139,16 @@ nsresult DownloadPlatform::DownloadDone(
         if (NS_SUCCEEDED(pathString->SetData(path))) {
           (void)obs->NotifyObservers(pathString, "download-watcher-notify",
                                      MOZ_UTF16("modified"));
         }
       }
     }
   }
 
-#ifdef XP_WIN
-  // Adjust file attributes so that by default, new files are indexed by
-  // desktop search services. Skip off those that land in the temp folder.
-  nsCOMPtr<nsIFile> tempDir, fileDir;
-  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
-  NS_ENSURE_SUCCESS(rv, rv);
-  aTarget->GetParent(getter_AddRefs(fileDir));
-
-  bool isTemp = false;
-  if (fileDir) {
-    fileDir->Equals(tempDir, &isTemp);
-  }
-
-  nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(aTarget));
-  if (!isTemp && localFileWin) {
-    localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
-  }
-#endif
-
 #endif
 
   return NS_OK;
 }
 
 nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL,
                                         uint32_t* aZone)
 {
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -188,18 +188,19 @@ add_task(function test_basic_tryToKeepPa
   do_check_eq(32, download.saver.getSha256Hash().length);
 });
 
 /**
  * Tests the permissions of the final target file once the download finished.
  */
 add_task(function test_unix_permissions()
 {
-  // This test is only executed on Linux and Mac.
-  if (Services.appinfo.OS != "Darwin" && Services.appinfo.OS != "Linux") {
+  // This test is only executed on some Desktop systems.
+  if (Services.appinfo.OS != "Darwin" && Services.appinfo.OS != "Linux" &&
+      Services.appinfo.OS != "WINNT") {
     do_print("Skipping test.");
     return;
   }
 
   let launcherPath = getTempFile("app-launcher").path;
 
   for (let autoDelete of [false, true]) {
     for (let isPrivate of [false, true]) {
@@ -223,22 +224,30 @@ add_task(function test_unix_permissions(
           download = yield promiseStartLegacyDownload(httpUrl("source.txt"), {
             isPrivate,
             launchWhenSucceeded,
             launcherPath: launchWhenSucceeded && launcherPath,
           });
           yield promiseDownloadStopped(download);
         }
 
-        // Temporary downloads should be read-only and not accessible to other
-        // users, while permanently downloaded files should be readable and
-        // writable as specified by the system umask.
         let isTemporary = launchWhenSucceeded && (autoDelete || isPrivate);
-        do_check_eq((yield OS.File.stat(download.target.path)).unixMode,
-                    isTemporary ? 0o400 : (0o666 & ~OS.Constants.Sys.umask));
+        let stat = yield OS.File.stat(download.target.path);
+        if (Services.appinfo.OS == "WINNT") {
+          // On Windows
+          // Temporary downloads should be read-only
+          do_check_eq(stat.winAttributes.readOnly, isTemporary ? true : false);
+        } else {
+          // On Linux, Mac
+          // Temporary downloads should be read-only and not accessible to other
+          // users, while permanently downloaded files should be readable and
+          // writable as specified by the system umask.
+          do_check_eq(stat.unixMode,
+                      isTemporary ? 0o400 : (0o666 & ~OS.Constants.Sys.umask));
+        }
       }
     }
   }
 
   // Clean up the changes to the preference.
   Services.prefs.clearUserPref(kDeleteTempFileOnExit);
 });
 
--- a/toolkit/components/reader/AboutReader.jsm
+++ b/toolkit/components/reader/AboutReader.jsm
@@ -599,17 +599,17 @@ AboutReader.prototype = {
     } else {
       article = yield this._getArticle(url);
     }
 
     if (this._windowUnloaded) {
       return;
     }
 
-    if (article && article.url == url) {
+    if (article) {
       this._showContent(article);
     } else if (this._articlePromise) {
       // If we were promised an article, show an error message if there's a failure.
       this._showError();
     } else {
       // Otherwise, just load the original URL. We can encounter this case when
       // loading an about:reader URL directly (e.g. opening a reading list item).
       this._win.location.href = url;