Bug 1241455 Send TLS Error Reports for subresources r=past, Gijs, mcmanus
authorMark Goodwin <mgoodwin@mozilla.com>
Mon, 01 Feb 2016 11:18:50 +0000
changeset 282615 c79d6954e4b1dc2779744cdec700eb0f209342cf
parent 282614 fe8c4493ef2fce3166dc7e148a3b7591fe708adf
child 282616 900810a353c30e0ec8d15075110e5f9838cca7b1
push id17362
push usercbook@mozilla.com
push dateTue, 02 Feb 2016 10:54:53 +0000
treeherderfx-team@e5f1b4782e38 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast, Gijs, mcmanus
bugs1241455
milestone47.0a1
Bug 1241455 Send TLS Error Reports for subresources r=past, Gijs, mcmanus This patch makes use of the security reporter component (which hasn't landed yet - see blockers) to allow automatic TLS error reports to be sent directly from nsHttpChannel.cpp rather than sending them from browser.js. This allows failed connections for subresources to be reported. Some of the report sending from browser.js was retained to allow reports to be sent at the time a user enables reporting. This has been modified to also make use of the component. Since the patient is on the table, I've also taken the opportunity to remove the retry and status bits from aboutCertError.xhtml and aboutNetError.xhtml - which removes a bunch of code and simplifies things a bit. The mochitests have been modified to cope with the fact that the UI does not update with report sending / report failures - instead, success and failure are determined by examining the response seen from the server from within the test.
browser/base/content/aboutNetError.xhtml
browser/base/content/aboutcerterror/aboutCertError.xhtml
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/test/general/browser_ssl_error_reports.js
browser/base/content/test/general/ssl_error_reports.sjs
browser/locales/en-US/chrome/browser/aboutCertError.dtd
browser/locales/en-US/chrome/overrides/netError.dtd
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -139,22 +139,16 @@
 
         if (allowOverride) {
           document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
           var overrideLink = document.getElementById("overrideWeakCrypto");
           overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
         }
       }
 
-      function sendErrorReport() {
-        var event = new CustomEvent("AboutNetErrorSendReport", {bubbles:true});
-
-        document.dispatchEvent(event);
-      }
-
       function initPage()
       {
         var err = getErrorCode();
 
         // if it's an unknown error or there's no title or description
         // defined, get the generic message
         var errTitle = document.getElementById("et_" + err);
         var errDesc  = document.getElementById("ed_" + err);
@@ -255,23 +249,17 @@
                 // set the checkbox
                 checkbox.checked = true;
               }
 
               checkbox.addEventListener('change', function(evt) {
                   var event = new CustomEvent("AboutNetErrorSetAutomatic",
                     {bubbles:true, detail:evt.target.checked});
                   document.dispatchEvent(event);
-                  if (evt.target.checked) {
-                    sendErrorReport();
-                  }
                 }, false);
-
-              var retryBtn = document.getElementById('reportCertificateErrorRetry');
-              retryBtn.addEventListener('click', sendErrorReport, false);
             }
           }
           if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
             showAdvancedButton(getErrorCode() == "weakCryptoUsed");
           }
         }.bind(this), true, true);
 
         var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
@@ -522,22 +510,16 @@
       </script>
 
       <!-- UI for option to report certificate errors to Mozilla. Removed on
            init for other error types .-->
       <div id="certificateErrorReporting">
         <p>
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
-
-          <span id="reportingState">
-            <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
-            <span id="reportSendingMessage">&errorReporting.sending;</span>
-            <span id="reportSentMessage">&errorReporting.sent;</span>
-          </span>
         </p>
       </div>
 
       <div id="weakCryptoAdvancedPanel">
         <div id="weakCryptoAdvancedDescription">
           <p>&weakCryptoAdvanced.longDesc;</p>
         </div>
         <div id="advancedLongDesc" />
--- a/browser/base/content/aboutcerterror/aboutCertError.xhtml
+++ b/browser/base/content/aboutcerterror/aboutCertError.xhtml
@@ -91,23 +91,16 @@
         var checkbox = document.getElementById("automaticallyReportInFuture");
         checkbox.addEventListener("change", function ({target: {checked}}) {
           document.dispatchEvent(new CustomEvent("AboutCertErrorSetAutomatic", {
             detail: checked,
             bubbles: true
           }));
         });
 
-        var retryBtn = document.getElementById("reportCertificateErrorRetry");
-        retryBtn.addEventListener("click", function () {
-          document.dispatchEvent(new CustomEvent("AboutCertErrorSendReport", {
-            bubbles: true
-          }));
-        });
-
         addEventListener("AboutCertErrorOptions", function (event) {
           var options = JSON.parse(event.detail);
           if (options && options.enabled) {
             // Display error reporting UI
             document.getElementById("certificateErrorReporting").style.display = "block";
 
             // set the checkbox
             checkbox.checked = !!options.automatic;
@@ -282,22 +275,16 @@
         </div>
       </div>
 
       <!-- UI for option to report certificate errors to Mozilla. -->
       <div id="certificateErrorReporting">
         <p>
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
-
-          <span id="reportingState">
-            <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
-            <span id="reportSendingMessage">&errorReporting.sending;</span>
-            <span id="reportSentMessage">&errorReporting.sent;</span>
-          </span>
         </p>
       </div>
 
       <!-- Advanced panel, which is hidden by default -->
       <div id="advancedPanel" style="visibility: hidden;">
         <p id="technicalContentText"/>
         <button id="exceptionDialogButton">&certerror.addException.label;</button>
       </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2706,110 +2706,33 @@ var BrowserOnClick = {
       break;
       case "Browser:SSLErrorGoBack":
         goBackFromErrorPage();
       break;
     }
   },
 
   onSSLErrorReport: function(browser, documentURI, location, securityInfo) {
-    function showReportStatus(reportStatus) {
-      gBrowser.selectedBrowser
-          .messageManager
-          .sendAsyncMessage("Browser:SSLErrorReportStatus",
-                            {
-                              reportStatus: reportStatus,
-                              documentURI: documentURI
-                            });
-    }
-
     if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
-      showReportStatus("error");
       Cu.reportError("User requested certificate error report sending, but certificate error reporting is disabled");
       return;
     }
 
-    let bin = TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND;
-    if (Services.prefs.getBoolPref("security.ssl.errorReporting.automatic")) {
-      bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND;
-    }
-    Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI").add(bin);
-
     let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                            .getService(Ci.nsISerializationHelper);
     let transportSecurityInfo = serhelper.deserializeObject(securityInfo);
     transportSecurityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
 
-    showReportStatus("activity");
-
-    /*
-     * Requested info for the report:
-     * - Domain of bad connection
-     * - Error type (e.g. Pinning, domain mismatch, etc)
-     * - Cert chain (at minimum, same data to distrust each cert in the
-     *   chain)
-     * - Request data (e.g. User Agent, IP, Timestamp)
-     *
-     * The request data should be added to the report by the receiving server.
-     */
-
-    // Convert the nsIX509CertList into a format that can be parsed into
-    // JSON
-    let asciiCertChain = [];
-
-    if (transportSecurityInfo.failedCertChain) {
-      let certs = transportSecurityInfo.failedCertChain.getEnumerator();
-      while (certs.hasMoreElements()) {
-        let cert = certs.getNext();
-        cert.QueryInterface(Ci.nsIX509Cert);
-        asciiCertChain.push(btoa(getDERString(cert)));
-      }
-    }
-
-    let report = {
-      hostname: location.hostname,
-      port: location.port,
-      timestamp: Math.round(Date.now() / 1000),
-      errorCode: transportSecurityInfo.errorCode,
-      failedCertChain: asciiCertChain,
-      userAgent: window.navigator.userAgent,
-      version: 1,
-      build: gAppInfo.appBuildID,
-      product: gAppInfo.name,
-      channel: UpdateUtils.UpdateChannel
-    }
-
-    let reportURL = Services.prefs.getCharPref("security.ssl.errorReporting.url");
-
-    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
-        .createInstance(Ci.nsIXMLHttpRequest);
-    try {
-      xhr.open("POST", reportURL);
-    } catch (e) {
-      Cu.reportError("xhr.open exception", e);
-      showReportStatus("error");
-    }
-
-    xhr.onerror = function (e) {
-      // error making request to reportURL
-      Cu.reportError("xhr onerror", e);
-      showReportStatus("error");
-    };
-
-    xhr.onload = function (event) {
-      if (xhr.status !== 201 && xhr.status !== 0) {
-        // request returned non-success status
-        Cu.reportError("xhr returned failure code", xhr.status);
-        showReportStatus("error");
-      } else {
-        showReportStatus("complete");
-      }
-    };
-
-    xhr.send(JSON.stringify(report));
+    let errorReporter = Cc["@mozilla.org/securityreporter;1"]
+                          .getService(Ci.nsISecurityReporter);
+    // if location.port is the empty string, set to -1 (for consistency with
+    // port values from nsIURI)
+    let port = location.port === "" ? -1 : location.port;
+    errorReporter.reportTLSError(transportSecurityInfo,
+                                 location.hostname, port);
   },
 
   onAboutCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
 
     switch (elementId) {
       case "exceptionDialogButton":
         if (isTopFrame) {
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -205,20 +205,18 @@ Cc["@mozilla.org/eventlistenerservice;1"
 const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
 const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
 const TLS_ERROR_REPORT_TELEMETRY_SUCCESS  = 6;
 const TLS_ERROR_REPORT_TELEMETRY_FAILURE  = 7;
 
 var AboutCertErrorListener = {
   init(chromeGlobal) {
     addMessageListener("AboutCertErrorDetails", this);
-    addMessageListener("Browser:SSLErrorReportStatus", this);
     chromeGlobal.addEventListener("AboutCertErrorLoad", this, false, true);
     chromeGlobal.addEventListener("AboutCertErrorSetAutomatic", this, false, true);
-    chromeGlobal.addEventListener("AboutCertErrorSendReport", this, false, true);
   },
 
   get isAboutCertError() {
     return content.document.documentURI.startsWith("about:certerror");
   },
 
   handleEvent(event) {
     if (!this.isAboutCertError) {
@@ -227,134 +225,90 @@ var AboutCertErrorListener = {
 
     switch (event.type) {
       case "AboutCertErrorLoad":
         this.onLoad(event);
         break;
       case "AboutCertErrorSetAutomatic":
         this.onSetAutomatic(event);
         break;
-      case "AboutCertErrorSendReport":
-        this.onSendReport();
-        break;
     }
   },
 
   receiveMessage(msg) {
     if (!this.isAboutCertError) {
       return;
     }
 
     switch (msg.name) {
       case "AboutCertErrorDetails":
         this.onDetails(msg);
         break;
-      case "Browser:SSLErrorReportStatus":
-        this.onReportStatus(msg);
-        break;
     }
   },
 
   onLoad(event) {
     let originalTarget = event.originalTarget;
     let ownerDoc = originalTarget.ownerDocument;
     ClickEventHandler.onAboutCertError(originalTarget, ownerDoc);
 
+    // Set up the TLS Error Reporting UI - reports are sent automatically
+    // (from nsHttpChannel::OnStopRequest) if the user has previously enabled
+    // automatic sending of reports. The UI ensures that a report is sent
+    // for the certificate error currently displayed if the user enables it
+    // here.
     let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
     content.dispatchEvent(new content.CustomEvent("AboutCertErrorOptions", {
       detail: JSON.stringify({
         enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
         automatic,
       })
     }));
-
-    if (automatic) {
-      this.onSendReport();
-    }
   },
 
   onDetails(msg) {
     let div = content.document.getElementById("certificateErrorText");
     div.textContent = msg.data.info;
   },
 
   onSetAutomatic(event) {
-    if (event.detail) {
-      this.onSendReport();
-    }
-
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
       automatic: event.detail
     });
-  },
-
-  onSendReport() {
-    let doc = content.document;
-    let location = doc.location.href;
-
-    let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
-                     .getService(Ci.nsISerializationHelper);
-
-    let serializable =  docShell.failedChannel.securityInfo
-                                .QueryInterface(Ci.nsITransportSecurityInfo)
-                                .QueryInterface(Ci.nsISerializable);
 
-    let serializedSecurityInfo = serhelper.serializeToString(serializable);
+    // if we're enabling reports, send a report for this failure
+    if (event.detail) {
+      let doc = content.document;
+      let location = doc.location.href;
 
-    sendAsyncMessage("Browser:SendSSLErrorReport", {
-      documentURI: doc.documentURI,
-      location: {hostname: doc.location.hostname, port: doc.location.port},
-      securityInfo: serializedSecurityInfo
-    });
-  },
-
-  onReportStatus(msg) {
-    let doc = content.document;
-    if (doc.documentURI != msg.data.documentURI) {
-      return;
-    }
+      let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+          .getService(Ci.nsISerializationHelper);
 
-    let reportSendingMsg = doc.getElementById("reportSendingMessage");
-    let reportSentMsg = doc.getElementById("reportSentMessage");
-    let retryBtn = doc.getElementById("reportCertificateErrorRetry");
+      let serializable =  docShell.failedChannel.securityInfo
+          .QueryInterface(Ci.nsITransportSecurityInfo)
+          .QueryInterface(Ci.nsISerializable);
+
+      let serializedSecurityInfo = serhelper.serializeToString(serializable);
 
-    switch (msg.data.reportStatus) {
-      case "activity":
-        // Hide the button that was just clicked
-        retryBtn.style.removeProperty("display");
-        reportSentMsg.style.removeProperty("display");
-        reportSendingMsg.style.display = "block";
-        break;
-      case "error":
-        // show the retry button
-        retryBtn.style.display = "block";
-        reportSendingMsg.style.removeProperty("display");
-        sendAsyncMessage("Browser:SSLErrorReportTelemetry",
-                         {reportStatus: TLS_ERROR_REPORT_TELEMETRY_FAILURE});
-        break;
-      case "complete":
-        // Show a success indicator
-        reportSentMsg.style.display = "block";
-        reportSendingMsg.style.removeProperty("display");
-        sendAsyncMessage("Browser:SSLErrorReportTelemetry",
-                         {reportStatus: TLS_ERROR_REPORT_TELEMETRY_SUCCESS});
-        break;
+      sendAsyncMessage("Browser:SendSSLErrorReport", {
+        documentURI: doc.documentURI,
+        location: {hostname: doc.location.hostname, port: doc.location.port},
+        securityInfo: serializedSecurityInfo
+      });
     }
-  }
+  },
 };
 
 AboutCertErrorListener.init(this);
 
 
 var AboutNetErrorListener = {
   init: function(chromeGlobal) {
     chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
     chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
-    chromeGlobal.addEventListener('AboutNetErrorSendReport', this, false, true);
-    chromeGlobal.addEventListener('AboutNetErrorUIExpanded', this, false, true);
     chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
   },
 
   get isAboutNetError() {
     return content.document.documentURI.startsWith("about:neterror");
   },
 
   handleEvent: function(aEvent) {
@@ -364,117 +318,65 @@ var AboutNetErrorListener = {
 
     switch (aEvent.type) {
     case "AboutNetErrorLoad":
       this.onPageLoad(aEvent);
       break;
     case "AboutNetErrorSetAutomatic":
       this.onSetAutomatic(aEvent);
       break;
-    case "AboutNetErrorSendReport":
-      this.onSendReport(aEvent);
-      break;
-    case "AboutNetErrorUIExpanded":
-      sendAsyncMessage("Browser:SSLErrorReportTelemetry",
-                       {reportStatus: TLS_ERROR_REPORT_TELEMETRY_EXPANDED});
-      break;
     case "AboutNetErrorOverride":
       this.onOverride(aEvent);
       break;
     }
   },
 
   onPageLoad: function(evt) {
     let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
     content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions", {
-            detail: JSON.stringify({
-              enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
-            automatic: automatic
-            })
-          }
-    ));
+      detail: JSON.stringify({
+        enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
+        automatic: automatic
+      })
+    }));
 
     sendAsyncMessage("Browser:SSLErrorReportTelemetry",
                      {reportStatus: TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN});
-
-    if (automatic) {
-      this.onSendReport(evt);
-    }
-    // hide parts of the UI we don't need yet
-    let contentDoc = content.document;
-
-    let reportSendingMsg = contentDoc.getElementById("reportSendingMessage");
-    let reportSentMsg = contentDoc.getElementById("reportSentMessage");
-    let retryBtn = contentDoc.getElementById("reportCertificateErrorRetry");
-    reportSendingMsg.style.display = "none";
-    reportSentMsg.style.display = "none";
-    retryBtn.style.display = "none";
   },
 
   onSetAutomatic: function(evt) {
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
-        automatic: evt.detail
-      });
-  },
-
-  onSendReport: function(evt) {
-    let contentDoc = content.document;
-
-    let reportSendingMsg = contentDoc.getElementById("reportSendingMessage");
-    let reportSentMsg = contentDoc.getElementById("reportSentMessage");
-    let retryBtn = contentDoc.getElementById("reportCertificateErrorRetry");
-
-    addMessageListener("Browser:SSLErrorReportStatus", function(message) {
-      // show and hide bits - but only if this is a message for the right
-      // document - we'll compare on document URI
-      if (contentDoc.documentURI === message.data.documentURI) {
-        switch(message.data.reportStatus) {
-        case "activity":
-          // Hide the button that was just clicked
-          retryBtn.style.display = "none";
-          reportSentMsg.style.display = "none";
-          reportSendingMsg.style.removeProperty("display");
-          break;
-        case "error":
-          // show the retry button
-          retryBtn.style.removeProperty("display");
-          reportSendingMsg.style.display = "none";
-          sendAsyncMessage("Browser:SSLErrorReportTelemetry",
-                           {reportStatus: TLS_ERROR_REPORT_TELEMETRY_FAILURE});
-          break;
-        case "complete":
-          // Show a success indicator
-          reportSentMsg.style.removeProperty("display");
-          reportSendingMsg.style.display = "none";
-          sendAsyncMessage("Browser:SSLErrorReportTelemetry",
-                           {reportStatus: TLS_ERROR_REPORT_TELEMETRY_SUCCESS});
-          break;
-        }
-      }
+      automatic: evt.detail
     });
 
-    let location = contentDoc.location.href;
+    // if we're enabling reports, send a report for this failure
+    if (evt.detail) {
+      let contentDoc = content.document;
+
+      let location = contentDoc.location.href;
 
-    let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
-                      .getService(Ci.nsISerializationHelper);
+      let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+                        .getService(Ci.nsISerializationHelper);
 
-    let serializable = docShell.failedChannel.securityInfo
-                               .QueryInterface(Ci.nsITransportSecurityInfo)
-                               .QueryInterface(Ci.nsISerializable);
+      let serializable = docShell.failedChannel.securityInfo
+          .QueryInterface(Ci.nsITransportSecurityInfo)
+          .QueryInterface(Ci.nsISerializable);
 
-    let serializedSecurityInfo = serhelper.serializeToString(serializable);
+      let serializedSecurityInfo = serhelper.serializeToString(serializable);
 
-    sendAsyncMessage("Browser:SendSSLErrorReport", {
-      documentURI: contentDoc.documentURI,
-      location: {
-        hostname: contentDoc.location.hostname,
-        port: contentDoc.location.port
-      },
-      securityInfo: serializedSecurityInfo
-    });
+      sendAsyncMessage("Browser:SendSSLErrorReport", {
+        documentURI: contentDoc.documentURI,
+        location: {
+          hostname: contentDoc.location.hostname,
+          port: contentDoc.location.port
+        },
+        securityInfo: serializedSecurityInfo
+      });
+
+    }
   },
 
   onOverride: function(evt) {
     let contentDoc = content.document;
     let location = contentDoc.location;
 
     sendAsyncMessage("Browser:OverrideWeakCrypto", {
       documentURI: contentDoc.documentURI,
--- a/browser/base/content/test/general/browser_ssl_error_reports.js
+++ b/browser/base/content/test/general/browser_ssl_error_reports.js
@@ -23,23 +23,22 @@ function cleanup() {
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
   cleanup();
 });
 
 add_task(function* test_send_report_neterror() {
   yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror");
   yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror");
-  yield testSendReportFailRetry(URL_NO_CERT, "nocert", "neterror");
   yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror");
 });
 
+
 add_task(function* test_send_report_certerror() {
   yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror");
-  yield testSendReportFailRetry(URL_BAD_CERT, "badcert", "certerror");
   yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror");
 });
 
 add_task(function* test_send_disabled() {
   Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false);
   Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
   Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid");
 
@@ -60,78 +59,55 @@ function* testSendReportAutomatically(te
   Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
   Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
 
   // Add a tab and wait until it's loaded.
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
   let browser = tab.linkedBrowser;
 
   // Load the page and wait for the error report submission.
-  let promiseReport = createErrorReportPromise(browser);
+  let promiseStatus = createReportResponseStatusPromise(URL_REPORTS + suffix);
   browser.loadURI(testURL);
-  yield promiseReport;
-  ok(true, "SSL error report submitted successfully");
+
+  ok(!isErrorStatus(yield promiseStatus),
+     "SSL error report submitted successfully");
 
   // Check that we loaded the right error page.
   yield checkErrorPage(browser, errorURISuffix);
 
   // Cleanup.
   gBrowser.removeTab(tab);
   cleanup();
 };
 
-function* testSendReportFailRetry(testURL, suffix, errorURISuffix) {
-  try {
-    yield testSendReportAutomatically(testURL, "error", errorURISuffix);
-    ok(false, "sending a report should have failed");
-  } catch (err) {
-    ok(err, "saw a failure notification");
-  }
-
-  Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
-
-  let browser = gBrowser.selectedBrowser;
-  let promiseReport = createErrorReportPromise(browser);
-  let promiseRetry = ContentTask.spawn(browser, null, function* () {
-    content.document.getElementById("reportCertificateErrorRetry").click();
-  });
-
-  yield Promise.all([promiseReport, promiseRetry]);
-  ok(true, "SSL error report submitted successfully");
-
-  // Cleanup.
-  gBrowser.removeCurrentTab();
-  cleanup();
-}
-
 function* testSetAutomatic(testURL, suffix, errorURISuffix) {
   Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
   Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
   Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
 
   // Add a tab and wait until it's loaded.
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
   let browser = tab.linkedBrowser;
 
   // Load the page.
   browser.loadURI(testURL);
   yield promiseErrorPageLoaded(browser);
 
   // Check that we loaded the right error page.
   yield checkErrorPage(browser, errorURISuffix);
 
+  let statusPromise = createReportResponseStatusPromise(URL_REPORTS + suffix);
+
   // Click the checkbox, enable automatic error reports.
-  let promiseReport = createErrorReportPromise(browser);
   yield ContentTask.spawn(browser, null, function* () {
     content.document.getElementById("automaticallyReportInFuture").click();
   });
 
   // Wait for the error report submission.
-  yield promiseReport;
-  ok(true, "SSL error report submitted successfully");
+  yield statusPromise;
 
   let isAutomaticReportingEnabled = () =>
     Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC);
 
   // Check that the pref was flipped.
   ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled");
 
   // Disable automatic error reports.
@@ -165,42 +141,32 @@ function* testSendReportDisabled(testURL
     return content.getComputedStyle(section).display == "none";
   });
   ok(hidden, "error reporting section should be hidden");
 
   // Cleanup.
   gBrowser.removeTab(tab);
 }
 
-function createErrorReportPromise(browser) {
-  return ContentTask.spawn(browser, null, function* () {
-    let type = "Browser:SSLErrorReportStatus";
-    let active = false;
+function isErrorStatus(status) {
+  return status < 200 || status >= 300;
+}
 
-    yield new Promise((resolve, reject) => {
-      addMessageListener(type, function onReportStatus(message) {
-        switch (message.data.reportStatus) {
-          case "activity":
-            active = true;
-            break;
-          case "complete":
-            removeMessageListener(type, onReportStatus);
-            if (active) {
-              resolve(message.data.reportStatus);
-            } else {
-              reject("activity should be seen before success");
-            }
-            break;
-          case "error":
-            removeMessageListener(type, onReportStatus);
-            reject("sending the report failed");
-            break;
-        }
-      });
-    });
+// use the observer service to see when a report is sent
+function createReportResponseStatusPromise(expectedURI) {
+  return new Promise(resolve => {
+    let observer = (subject, topic, data) => {
+      subject.QueryInterface(Ci.nsIHttpChannel);
+      let requestURI = subject.URI.spec;
+      if (requestURI == expectedURI) {
+        Services.obs.removeObserver(observer, "http-on-examine-response");
+        resolve(subject.responseStatus);
+      }
+    };
+    Services.obs.addObserver(observer, "http-on-examine-response", false);
   });
 }
 
 function promiseErrorPageLoaded(browser) {
   return new Promise(resolve => {
     browser.addEventListener("DOMContentLoaded", function onLoad() {
       browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
       resolve();
--- a/browser/base/content/test/general/ssl_error_reports.sjs
+++ b/browser/base/content/test/general/ssl_error_reports.sjs
@@ -80,12 +80,12 @@ function handleRequest(request, response
       response.write("<html>OK</html>");
       break;
     case "error":
       response.setStatusLine("1.1", 500, "Server error");
       response.write("<html>server error</html>");
       break;
     default:
       response.setStatusLine("1.1", 500, "Server error");
-      response.write("<html>succeed, nocert or error expected</html>");
+      response.write("<html>succeed, nocert or error expected (got " + request.queryString + ")</html>");
       break;
   }
 }
--- a/browser/locales/en-US/chrome/browser/aboutCertError.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutCertError.dtd
@@ -31,11 +31,8 @@ can tell &brandShortName; to start trust
 <b>Even if you trust the site, this error could mean that someone is
 tampering with your connection.</b>">
 <!ENTITY certerror.expert.contentPara2 "Don't add an exception unless
 you know there's a good reason why this site doesn't use trusted identification.">
 <!ENTITY certerror.addException.label "Add Exception…">
 <!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
 
 <!ENTITY errorReporting.automatic "Report errors like this to help Mozilla identify misconfigured sites">
-<!ENTITY errorReporting.sending "Sending report">
-<!ENTITY errorReporting.sent "Report sent">
-<!ENTITY errorReporting.tryAgain "Try again">
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -201,19 +201,16 @@ functionality specific to firefox. -->
 <p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
 
 <button id='getMeOutOfHereButton'>&securityOverride.getMeOutOfHereButton;</button>
 <button id='exceptionDialogButton'>&securityOverride.exceptionButtonLabel;</button>
 ">
 
 <!ENTITY errorReporting.automatic2 "Report errors like this to help Mozilla identify and block malicious sites">
 <!ENTITY errorReporting.learnMore "Learn more…">
-<!ENTITY errorReporting.sending "Sending report">
-<!ENTITY errorReporting.sent "Report sent">
-<!ENTITY errorReporting.tryAgain "Try again">
 
 <!ENTITY remoteXUL.title "Remote XUL">
 <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
 
 <!ENTITY sslv3Used.title "Unable to Connect Securely">
 <!-- LOCALIZATION NOTE (sslv3Used.longDesc2) - Do not translate
      "SSL_ERROR_UNSUPPORTED_VERSION". -->
 <!ENTITY sslv3Used.longDesc2 "Advanced info: SSL_ERROR_UNSUPPORTED_VERSION">
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -14,16 +14,18 @@
 #include "nsIApplicationCacheService.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsICacheStorageService.h"
 #include "nsICacheStorage.h"
 #include "nsICacheEntry.h"
 #include "nsICaptivePortalService.h"
 #include "nsICryptoHash.h"
 #include "nsINetworkInterceptController.h"
+#include "nsINSSErrorsService.h"
+#include "nsISecurityReporter.h"
 #include "nsIStringBundle.h"
 #include "nsIStreamListenerTee.h"
 #include "nsISeekableStream.h"
 #include "nsILoadGroupChild.h"
 #include "nsIProtocolProxyService2.h"
 #include "nsIURIClassifier.h"
 #include "nsMimeTypes.h"
 #include "nsNetCID.h"
@@ -1318,16 +1320,66 @@ nsHttpChannel::ProcessSecurityHeaders()
 
     rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
                                      sslStatus, flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+/**
+ * Decide whether or not to send a security report and, if so, give the
+ * SecurityReporter the information required to send such a report.
+ */
+void
+nsHttpChannel::ProcessSecurityReport(nsresult status) {
+    uint32_t errorClass;
+    nsCOMPtr<nsINSSErrorsService> errSvc =
+            do_GetService("@mozilla.org/nss_errors_service;1");
+    // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+    // not in the set of errors covered by the NSS errors service.
+    nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+    if (!NS_SUCCEEDED(rv)) {
+        return;
+    }
+
+    // if the content was not loaded succesfully and we have security info,
+    // send a TLS error report - we must do this early as other parts of
+    // OnStopRequest can return early
+    bool reportingEnabled =
+            Preferences::GetBool("security.ssl.errorReporting.enabled");
+    bool reportingAutomatic =
+            Preferences::GetBool("security.ssl.errorReporting.automatic");
+    if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
+        return;
+    }
+
+    nsCOMPtr<nsITransportSecurityInfo> secInfo =
+            do_QueryInterface(mSecurityInfo);
+    nsCOMPtr<nsISecurityReporter> errorReporter =
+            do_GetService("@mozilla.org/securityreporter;1");
+
+    if (!secInfo || !mURI) {
+        return;
+    }
+
+    nsAutoCString hostStr;
+    int32_t port;
+    rv = mURI->GetHost(hostStr);
+    if (!NS_SUCCEEDED(rv)) {
+        return;
+    }
+
+    rv = mURI->GetPort(&port);
+
+    if (NS_SUCCEEDED(rv)) {
+        errorReporter->ReportTLSError(secInfo, hostStr, port);
+    }
+}
+
 bool
 nsHttpChannel::IsHTTPS()
 {
     bool isHttps;
     if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
         return false;
     return true;
 }
@@ -5760,16 +5812,23 @@ NS_IMETHODIMP
 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
 {
     PROFILER_LABEL("nsHttpChannel", "OnStopRequest",
         js::ProfileEntry::Category::NETWORK);
 
     LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
         this, request, status));
 
+    MOZ_ASSERT(NS_IsMainThread(),
+               "OnStopRequest should only be called from the main thread");
+
+    if (NS_FAILED(status)) {
+        ProcessSecurityReport(status);
+    }
+
     if (mTimingEnabled && request == mCachePump) {
         mCacheReadEnd = TimeStamp::Now();
     }
 
     // allow content to be cached if it was loaded successfully (bug #482935)
     bool contentComplete = NS_SUCCEEDED(status);
 
     // honor the cancelation status even if the underlying transaction completed.
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -361,16 +361,21 @@ private:
      * A function that takes care of reading STS and PKP headers and enforcing
      * STS and PKP load rules. After a secure channel is erected, STS and PKP
      * requires the channel to be trusted or any STS or PKP header data on
      * the channel is ignored. This is called from ProcessResponse.
      */
     nsresult ProcessSecurityHeaders();
 
     /**
+     * A function that will, if the feature is enabled, send security reports.
+     */
+    void ProcessSecurityReport(nsresult status);
+
+    /**
      * A function to process a single security header (STS or PKP), assumes
      * some basic sanity checks have been applied to the channel. Called
      * from ProcessSecurityHeaders.
      */
     nsresult ProcessSingleSecurityHeader(uint32_t aType,
                                          nsISSLStatus *aSSLStatus,
                                          uint32_t aFlags);