Bug 1484255 - Add Telemetry Events for the certificate error pages. r=nhnt11,keeler
☠☠ backed out by 111455f16f4b ☠ ☠
authorJohann Hofmann <jhofmann@mozilla.com>
Mon, 15 Oct 2018 23:00:08 +0000
changeset 499817 1119f9458b5d76cef62577b526c508f7908f4710
parent 499816 5622861eb245cc20b3a121ffb73f1b58c9745934
child 499818 2fd7d3ae070e853281fa49408996ecc9308990a9
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnhnt11, keeler
bugs1484255
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484255 - Add Telemetry Events for the certificate error pages. r=nhnt11,keeler Differential Revision: https://phabricator.services.mozilla.com/D8281
browser/actors/NetErrorChild.jsm
browser/app/profile/firefox.js
browser/base/content/aboutNetError-new.xhtml
browser/base/content/aboutNetError.xhtml
browser/base/content/browser.js
browser/base/content/test/about/browser.ini
browser/base/content/test/about/browser_aboutCertError.js
browser/base/content/test/about/browser_aboutCertError_telemetry.js
browser/base/content/test/about/head.js
browser/components/nsBrowserGlue.js
docshell/base/nsDocShell.cpp
security/manager/ssl/nsISecurityUITelemetry.idl
toolkit/components/telemetry/Events.yaml
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -9,16 +9,18 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
                                "resource://gre/modules/BrowserUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "WebNavigationFrames",
                                "resource://gre/modules/WebNavigationFrames.jsm");
 
+XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
+
 XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() {
   return Services.strings.createBundle("chrome://pipnss/locale/pipnss.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
   return Services.strings.createBundle("chrome://branding/locale/brand.properties");
 });
 XPCOMUtils.defineLazyPreferenceGetter(this, "newErrorPagesEnabled",
   "browser.security.newcerterrorpage.enabled");
@@ -317,16 +319,17 @@ class NetErrorChild extends ActorChild {
     technicalInfo.append("\n");
 
     // Add link to certificate and error message.
     let linkPrefix = gPipNSSBundle.GetStringFromName("certErrorCodePrefix3");
     let detailLink = doc.createElement("a");
     detailLink.append(input.data.codeString);
     detailLink.title = input.data.codeString;
     detailLink.id = "errorCode";
+    detailLink.dataset.telemetryId = "error_code_link";
     let fragment = BrowserUtils.getLocalizedFragment(doc, linkPrefix, detailLink);
     technicalInfo.appendChild(fragment);
     var errorCode = doc.getElementById("errorCode");
     if (errorCode) {
       errorCode.href = "javascript:void(0)";
       errorCode.addEventListener("click", () => {
         let debugInfo = doc.getElementById("certificateErrorDebugInformation");
         debugInfo.style.display = "block";
@@ -348,16 +351,22 @@ class NetErrorChild extends ActorChild {
     this._setTechDetails(msg, doc);
     let learnMoreLink = doc.getElementById("learnMoreLink");
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     let errWhatToDo = doc.getElementById("es_nssBadCert_" + msg.data.codeString);
     let es = doc.getElementById("errorWhatToDoText");
     let errWhatToDoTitle = doc.getElementById("edd_nssBadCert");
     let est = doc.getElementById("errorWhatToDoTitleText");
 
+    doc.body.setAttribute("code", msg.data.codeString);
+
+    // Need to do this here (which is not exactly at load but a few ticks later),
+    // because this is the first time we have access to the error code.
+    this.recordLoadEvent(doc);
+
     switch (msg.data.code) {
       case SSL_ERROR_BAD_CERT_DOMAIN:
       case SEC_ERROR_OCSP_INVALID_SIGNING_CERT:
       case SEC_ERROR_UNKNOWN_ISSUER:
         if (!newErrorPagesEnabled) {
           break;
         }
         if (es) {
@@ -525,21 +534,23 @@ class NetErrorChild extends ActorChild {
       this.onSetAutomatic(aEvent);
       break;
     case "AboutNetErrorResetPreferences":
       this.onResetPreferences(aEvent);
       break;
     case "click":
       if (aEvent.button == 0) {
         if (this.isAboutCertError(doc)) {
+          this.recordClick(aEvent.originalTarget);
           this.onCertError(aEvent.originalTarget, doc.defaultView);
         } else {
           this.onClick(aEvent);
         }
       }
+      break;
     }
   }
 
   receiveMessage(msg) {
     if (msg.name == "CertErrorDetails") {
       let frameDocShell = WebNavigationFrames.findDocShell(msg.data.frameId, this.docShell);
       // We need nsIWebNavigation to access docShell.document.
       frameDocShell && frameDocShell.QueryInterface(Ci.nsIWebNavigation);
@@ -691,16 +702,48 @@ class NetErrorChild extends ActorChild {
       frameId: WebNavigationFrames.getFrameId(win),
       location: win.document.location.href,
       elementId: target.getAttribute("id"),
       isTopFrame: (win.parent === win),
       securityInfoAsString: getSerializedSecurityInfo(win.docShell),
     });
   }
 
+  getCSSClass(doc) {
+    let searchParams = new URL(doc.documentURI).searchParams;
+    return searchParams.get("s");
+  }
+
+  recordLoadEvent(doc) {
+    let cssClass = this.getCSSClass(doc);
+    // Telemetry values for events are max. 80 bytes.
+    let errorCode = doc.body.getAttribute("code").substring(0, 40);
+    Services.telemetry.recordEvent("security.ui.certerror", "load", "aboutcerterror", errorCode, {
+      "has_sts": (cssClass == "badStsCert").toString(),
+      "is_frame": (doc.ownerGlobal.parent != doc.ownerGlobal).toString(),
+    });
+  }
+
+  recordClick(element) {
+    let telemetryId = element.dataset.telemetryId;
+    if (!telemetryId) {
+      return;
+    }
+    let doc = element.ownerDocument;
+    let cssClass = this.getCSSClass(doc);
+    // Telemetry values for events are max. 80 bytes.
+    let errorCode = doc.body.getAttribute("code").substring(0, 40);
+    let panel = doc.getElementById("badCertAdvancedPanel");
+    Services.telemetry.recordEvent("security.ui.certerror", "click", telemetryId, errorCode, {
+      "panel_open": (panel.style.display == "none").toString(),
+      "has_sts": (cssClass == "badStsCert").toString(),
+      "is_frame": (doc.ownerGlobal.parent != doc.ownerGlobal).toString(),
+    });
+  }
+
   onClick(event) {
     let {documentURI} = event.target.ownerDocument;
 
     let elmId = event.originalTarget.getAttribute("id");
     if (elmId == "returnButton") {
       this.mm.sendAsyncMessage("Browser:SSLErrorGoBack", {});
       return;
     }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -952,16 +952,18 @@ pref("security.alternate_certificate_err
 
 // Enable the new certificate error page only for Nightly
 #if defined(NIGHTLY_BUILD)
 pref("browser.security.newcerterrorpage.enabled", true);
 #else
 pref("browser.security.newcerterrorpage.enabled", false);
 #endif
 
+pref("security.certerrors.recordEventTelemetry", true);
+
 // Whether to start the private browsing mode at application startup
 pref("browser.privatebrowsing.autostart", false);
 
 // Whether the bookmark panel should be shown when bookmarking a page.
 pref("browser.bookmarks.editDialog.showForNewBookmarks", true);
 
 // Don't try to alter this pref, it'll be reset the next time you use the
 // bookmarking dialog
--- a/browser/base/content/aboutNetError-new.xhtml
+++ b/browser/base/content/aboutNetError-new.xhtml
@@ -159,61 +159,61 @@
           <div id="wrongSystemTimeWithoutReferencePanel">
             &certerror.wrongSystemTimeWithoutReference;
           </div>
 
           <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
           <div id="errorLongDesc" />
 
           <div id="learnMoreContainer">
-            <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
+            <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new" data-telemetry-id="learn_more_link">&errorReporting.learnMore;</a></p>
           </div>
         </div>
 
         <!-- UI for option to report certificate errors to Mozilla. Removed on
              init for other error types .-->
         <div id="prefChangeContainer" class="button-container">
           <p>&prefReset.longDesc;</p>
           <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
         </div>
 
         <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
-          <button id="returnButton" class="primary" autocomplete="off">&returnToPreviousPage1.label;</button>
+          <button id="returnButton" class="primary" autocomplete="off" data-telemetry-id="return_button_top">&returnToPreviousPage1.label;</button>
           <button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label2;</button>
           <button id="errorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
-          <button id="advancedButton" autocomplete="off">&continue2.label;</button>
+          <button id="advancedButton" data-telemetry-id="advanced_button" autocomplete="off">&continue2.label;</button>
           <button id="moreInformationButton" autocomplete="off">&moreInformation.label;</button>
         </div>
       </div>
 
       <div id="netErrorButtonContainer" class="button-container">
         <button id="errorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
       </div>
 
       <div id="advancedPanelContainer">
         <div id="badCertAdvancedPanel" class="advanced-panel">
           <p id="badCertTechnicalInfo"/>
           <div id="advancedPanelButtonContainer" class="button-container">
-            <button id="advancedPanelReturnButton" class="primary" autocomplete="off">&returnToPreviousPage1.label;</button>
+            <button id="advancedPanelReturnButton" class="primary" autocomplete="off" data-telemetry-id="return_button_adv">&returnToPreviousPage1.label;</button>
             <button id="advancedPanelErrorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
             <div class="exceptionDialogButtonContainer">
-              <button id="exceptionDialogButton">&securityOverride.exceptionButton1Label;</button>
+              <button id="exceptionDialogButton" data-telemetry-id="exception_button">&securityOverride.exceptionButton1Label;</button>
             </div>
           </div>
         </div>
 
         <div id="certificateErrorReporting">
             <p class="toggle-container-with-text">
-                <input type="checkbox" id="automaticallyReportInFuture" role="checkbox" />
+                <input type="checkbox" id="automaticallyReportInFuture" role="checkbox" data-telemetry-id="auto_report_cb"/>
                 <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
             </p>
         </div>
 
         <div id="certificateErrorDebugInformation">
-          <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+          <button id="copyToClipboard" data-telemetry-id="clipboard_button_top">&certerror.copyToClipboard.label;</button>
           <div id="certificateErrorText"/>
-          <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+          <button id="copyToClipboard" data-telemetry-id="clipboard_button_bot">&certerror.copyToClipboard.label;</button>
         </div>
       </div>
     </div>
   </body>
   <script type="application/javascript" src="chrome://browser/content/aboutNetError.js"/>
 </html>
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -128,59 +128,59 @@
           <div id="wrongSystemTimeWithoutReferencePanel">
             &certerror.wrongSystemTimeWithoutReference;
           </div>
 
           <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
           <div id="errorLongDesc" />
 
           <div id="learnMoreContainer">
-            <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
+            <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new" data-telemetry-id="learn_more_link">&errorReporting.learnMore;</a></p>
           </div>
         </div>
 
         <!-- UI for option to report certificate errors to Mozilla. Removed on
              init for other error types .-->
         <div id="certificateErrorReporting">
           <p class="toggle-container-with-text">
-            <input type="checkbox" id="automaticallyReportInFuture" role="checkbox" />
+            <input type="checkbox" id="automaticallyReportInFuture" role="checkbox"  data-telemetry-id="auto_report_cb"/>
             <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
           </p>
         </div>
 
         <div id="prefChangeContainer" class="button-container">
           <p>&prefReset.longDesc;</p>
           <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
         </div>
 
         <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
-          <button id="returnButton" class="primary" autocomplete="off">&returnToPreviousPage.label;</button>
+          <button id="returnButton" class="primary" autocomplete="off" data-telemetry-id="return_button_top">&returnToPreviousPage.label;</button>
           <button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label2;</button>
-          <button id="advancedButton" autocomplete="off">&advanced.label;</button>
+          <button id="advancedButton" data-telemetry-id="advanced_button" autocomplete="off">&advanced.label;</button>
           <button id="moreInformationButton" autocomplete="off">&moreInformation.label;</button>
         </div>
       </div>
 
       <div id="netErrorButtonContainer" class="button-container">
         <button id="errorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
       </div>
 
       <div id="advancedPanelContainer">
         <div id="badCertAdvancedPanel" class="advanced-panel">
           <p id="badCertTechnicalInfo"/>
           <div id="advancedPanelButtonContainer" class="button-container">
             <button id="advancedPanelErrorTryAgain" class="primary" autocomplete="off">&retry.label;</button>
           </div>
           <div class="exceptionDialogButtonContainer">
-            <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
+            <button id="exceptionDialogButton" data-telemetry-id="exception_button">&securityOverride.exceptionButtonLabel;</button>
           </div>
         </div>
 
         <div id="certificateErrorDebugInformation">
-          <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+          <button id="copyToClipboard" data-telemetry-id="clipboard_button_top">&certerror.copyToClipboard.label;</button>
           <div id="certificateErrorText"/>
-          <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+          <button id="copyToClipboard" data-telemetry-id="clipboard_button_bot">&certerror.copyToClipboard.label;</button>
         </div>
       </div>
     </div>
   </body>
   <script type="application/javascript" src="chrome://browser/content/aboutNetError.js"/>
 </html>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3036,25 +3036,20 @@ var BrowserOnClick = {
       break;
       case "Browser:SSLErrorGoBack":
         goBackFromErrorPage();
       break;
     }
   },
 
   onCertError(browser, elementId, isTopFrame, location, securityInfoAsString, frameId) {
-    let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
     let securityInfo;
 
     switch (elementId) {
       case "exceptionDialogButton":
-        if (isTopFrame) {
-          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
-        }
-
         securityInfo = getSecurityInfo(securityInfoAsString);
         let params = { exceptionAdded: false,
                        securityInfo };
         if (Services.prefs.getBoolPref("browser.security.newcerterrorpage.enabled", false)) {
           let overrideService = Cc["@mozilla.org/security/certoverride;1"]
                                   .getService(Ci.nsICertOverrideService);
           let flags = 0;
           if (securityInfo.isUntrusted) {
@@ -3093,32 +3088,25 @@ var BrowserOnClick = {
 
         // If the user added the exception cert, attempt to reload the page
         if (params.exceptionAdded) {
           browser.reload();
         }
         break;
 
       case "returnButton":
-        if (isTopFrame) {
-          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
-        }
         goBackFromErrorPage();
         break;
 
       case "advancedPanelReturnButton":
         goBackFromErrorPage();
         break;
 
       case "advancedButton":
       case "moreInformationButton":
-        if (isTopFrame) {
-          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
-        }
-
         securityInfo = getSecurityInfo(securityInfoAsString);
         let errorInfo = getDetailedCertErrorInfo(location,
                                                  securityInfo);
         let validityInfo = {
           notAfter: securityInfo.serverCert.validity.notAfter,
           notBefore: securityInfo.serverCert.validity.notBefore,
           notAfterLocalTime: securityInfo.serverCert.validity.notAfterLocalTime,
           notBeforeLocalTime: securityInfo.serverCert.validity.notBeforeLocalTime,
--- a/browser/base/content/test/about/browser.ini
+++ b/browser/base/content/test/about/browser.ini
@@ -1,19 +1,19 @@
 [DEFAULT]
 support-files =
   head.js
   print_postdata.sjs
   searchSuggestionEngine.sjs
   searchSuggestionEngine.xml
   POSTSearchEngine.xml
+  dummy_page.html
 
 [browser_aboutCertError.js]
-support-files =
-  dummy_page.html
+[browser_aboutCertError_telemetry.js]
 [browser_aboutHome_search_POST.js]
 [browser_aboutHome_search_composing.js]
 [browser_aboutHome_search_searchbar.js]
 [browser_aboutHome_search_suggestion.js]
 skip-if = os == "mac" || (os == "linux" && (!debug || bits == 64)) # Bug 1399648, bug 1402502
 [browser_aboutHome_search_telemetry.js]
 [browser_aboutNetError.js]
 [browser_aboutStopReload.js]
--- a/browser/base/content/test/about/browser_aboutCertError.js
+++ b/browser/base/content/test/about/browser_aboutCertError.js
@@ -2,57 +2,21 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // This is testing the aboutCertError page (Bug 1207107).
 
 const GOOD_PAGE = "https://example.com/";
 const GOOD_PAGE_2 = "https://example.org/";
-const DUMMY_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", GOOD_PAGE) + "dummy_page.html";
 const BAD_CERT = "https://expired.example.com/";
 const UNKNOWN_ISSUER = "https://self-signed.example.com ";
 const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
 const {TabStateFlusher} = ChromeUtils.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
 
-function injectErrorPageFrame(tab, src) {
-  return ContentTask.spawn(tab.linkedBrowser, {frameSrc: src}, async function({frameSrc}) {
-    let loaded = ContentTaskUtils.waitForEvent(content.wrappedJSObject, "DOMFrameContentLoaded");
-    let iframe = content.document.createElement("iframe");
-    iframe.src = frameSrc;
-    content.document.body.appendChild(iframe);
-    await loaded;
-    // We will have race conditions when accessing the frame content after setting a src,
-    // so we can't wait for AboutNetErrorLoad. Let's wait for the certerror class to
-    // appear instead (which should happen at the same time as AboutNetErrorLoad).
-    await ContentTaskUtils.waitForCondition(() =>
-      iframe.contentDocument.body.classList.contains("certerror"));
-  });
-}
-
-async function openErrorPage(src, useFrame) {
-  let tab;
-  if (useFrame) {
-    info("Loading cert error page in an iframe");
-    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE);
-    await injectErrorPageFrame(tab, src);
-  } else {
-    let certErrorLoaded;
-    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
-      gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, src);
-      let browser = gBrowser.selectedBrowser;
-      certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
-    }, false);
-    info("Loading and waiting for the cert error");
-    await certErrorLoaded;
-  }
-
-  return tab;
-}
-
 add_task(async function checkReturnToAboutHome() {
   info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home");
   for (let useFrame of [false, true]) {
     let tab = await openErrorPage(BAD_CERT, useFrame);
     let browser = tab.linkedBrowser;
 
     is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
     is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/about/browser_aboutCertError_telemetry.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+const BAD_CERT = "https://expired.example.com/";
+const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
+
+add_task(async function checkTelemetryClickEvents() {
+  info("Loading a bad cert page and verifying telemetry click events arrive.");
+
+  let oldCanRecord = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
+
+  registerCleanupFunction(() => {
+    Services.telemetry.canRecordExtended = oldCanRecord;
+  });
+
+  // For obvious reasons event telemetry seems to sync with the content
+  // processes asynchronously, so we need to wait for the main process
+  // to catch up through the entire test.
+  Services.telemetry.clearEvents();
+  await TestUtils.waitForCondition(() => {
+    let events = Services.telemetry.snapshotEvents(
+      Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true).content;
+    return !events || !events.length;
+  });
+
+  for (let useFrame of [false, true]) {
+    let recordedObjects = [
+      "advanced_button",
+      "learn_more_link",
+      "auto_report_cb",
+      "error_code_link",
+      "clipboard_button_top",
+      "clipboard_button_bot",
+      "return_button_top",
+      "return_button_adv",
+    ];
+
+    if (!useFrame) {
+      recordedObjects.push("exception_button");
+    }
+
+    for (let object of recordedObjects) {
+      let tab = await openErrorPage(BAD_CERT, useFrame);
+      let browser = tab.linkedBrowser;
+
+      let loadEvents = await TestUtils.waitForCondition(() => {
+        let events = Services.telemetry.snapshotEvents(
+          Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true).content;
+        if (events && events.length) {
+          events = events.filter(e => e[1] == "security.ui.certerror" && e[2] == "load");
+          if (events.length == 1 && events[0][5].is_frame == useFrame.toString()) {
+            return events;
+          }
+        }
+        return null;
+      });
+
+      is(loadEvents.length, 1, `recorded telemetry for the load testing ${object}, useFrame: ${useFrame}`);
+
+      await ContentTask.spawn(browser, {frame: useFrame, objectId: object}, async function({frame, objectId}) {
+        let doc = frame ? content.document.querySelector("iframe").contentDocument : content.document;
+
+        await ContentTaskUtils.waitForCondition(() => doc.body.classList.contains("certerror"), "Wait for certerror to be loaded");
+
+        let domElement = doc.querySelector(`[data-telemetry-id='${objectId}']`);
+        domElement.click();
+      });
+
+      let clickEvents = await TestUtils.waitForCondition(() => {
+        let events = Services.telemetry.snapshotEvents(
+          Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true).content;
+        if (events && events.length) {
+          events = events.filter(e => e[1] == "security.ui.certerror" && e[2] == "click" && e[3] == object);
+          if (events.length == 1 && events[0][5].is_frame == useFrame.toString()) {
+            return events;
+          }
+        }
+        return null;
+      }, "Has captured telemetry events.");
+
+      is(clickEvents.length, 1, `recorded telemetry for the click on ${object}, useFrame: ${useFrame}`);
+
+      // We opened an extra tab for the SUMO page, need to close it.
+      if (object == "learn_more_link") {
+        BrowserTestUtils.removeTab(gBrowser.selectedTab);
+      }
+
+      if (object == "exception_button") {
+        let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                                    .getService(Ci.nsICertOverrideService);
+        certOverrideService.clearValidityOverride("expired.example.com", -1);
+      }
+
+      BrowserTestUtils.removeTab(gBrowser.selectedTab);
+    }
+  }
+});
--- a/browser/base/content/test/about/head.js
+++ b/browser/base/content/test/about/head.js
@@ -1,10 +1,47 @@
 /* eslint-env mozilla/frame-script */
 
+function injectErrorPageFrame(tab, src) {
+  return ContentTask.spawn(tab.linkedBrowser, {frameSrc: src}, async function({frameSrc}) {
+    let loaded = ContentTaskUtils.waitForEvent(content.wrappedJSObject, "DOMFrameContentLoaded");
+    let iframe = content.document.createElement("iframe");
+    iframe.src = frameSrc;
+    content.document.body.appendChild(iframe);
+    await loaded;
+    // We will have race conditions when accessing the frame content after setting a src,
+    // so we can't wait for AboutNetErrorLoad. Let's wait for the certerror class to
+    // appear instead (which should happen at the same time as AboutNetErrorLoad).
+    await ContentTaskUtils.waitForCondition(() =>
+      iframe.contentDocument.body.classList.contains("certerror"));
+  });
+}
+
+async function openErrorPage(src, useFrame) {
+  let dummyPage = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "dummy_page.html";
+
+  let tab;
+  if (useFrame) {
+    info("Loading cert error page in an iframe");
+    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, dummyPage);
+    await injectErrorPageFrame(tab, src);
+  } else {
+    let certErrorLoaded;
+    tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+      gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, src);
+      let browser = gBrowser.selectedBrowser;
+      certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+    }, false);
+    info("Loading and waiting for the cert error");
+    await certErrorLoaded;
+  }
+
+  return tab;
+}
+
 function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
   retryTimes = typeof retryTimes !== "undefined" ? retryTimes : 30;
   var tries = 0;
   var interval = setInterval(function() {
     if (tries >= retryTimes) {
       ok(false, errorMsg);
       moveOn();
     }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1548,16 +1548,23 @@ BrowserGlue.prototype = {
    * but it will not make it happen later (and out of order) compared
    * to the other ones scheduled together.
    */
   _scheduleStartupIdleTasks() {
     Services.tm.idleDispatchToMainThread(() => {
       ContextualIdentityService.load();
     });
 
+    Services.tm.idleDispatchToMainThread(() => {
+      let enableCertErrorUITelemetry =
+        Services.prefs.getBoolPref("security.certerrors.recordEventTelemetry", false);
+      Services.telemetry.setEventRecordingEnabled("security.ui.certerror",
+        enableCertErrorUITelemetry);
+    });
+
     // Load the Login Manager data from disk off the main thread, some time
     // after startup.  If the data is required before this runs, for example
     // because a restored page contains a password field, it will be loaded on
     // the main thread, and this initialization request will be ignored.
     Services.tm.idleDispatchToMainThread(() => {
       try {
         Services.logins;
       } catch (ex) {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4540,37 +4540,24 @@ nsDocShell::DisplayLoadError(nsresult aE
       // HSTS/pinning takes precedence over the expert bad cert pref. We
       // never want to show the "Add Exception" button for these sites.
       // In the future we should differentiate between an HSTS host and a
       // pinned host and display a more informative message to the user.
       if (isStsHost || isPinnedHost) {
         cssClass.AssignLiteral("badStsCert");
       }
 
-      uint32_t bucketId;
-      if (isStsHost) {
-        // measuring STS separately allows us to measure click through
-        // rates easily
-        bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP_STS;
-      } else {
-        bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP;
-      }
-
       // See if an alternate cert error page is registered
       nsAutoCString alternateErrorPage;
       nsresult rv =
         Preferences::GetCString("security.alternate_certificate_error_page",
                                 alternateErrorPage);
       if (NS_SUCCEEDED(rv)) {
         errorPage.Assign(alternateErrorPage);
       }
-
-      if (!IsFrame() && errorPage.EqualsIgnoreCase("certerror")) {
-        Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId);
-      }
     } else {
       error = "nssFailure2";
     }
   } else if (NS_ERROR_PHISHING_URI == aError ||
              NS_ERROR_MALWARE_URI == aError ||
              NS_ERROR_UNWANTED_URI == aError ||
              NS_ERROR_HARMFUL_URI == aError) {
     nsAutoCString host;
--- a/security/manager/ssl/nsISecurityUITelemetry.idl
+++ b/security/manager/ssl/nsISecurityUITelemetry.idl
@@ -111,23 +111,23 @@ const uint32_t WARNING_GEOLOCATION_REQUE
 // const uint32_t WARNING_MALWARE_PAGE_FRAME_GET_ME_OUT_OF_HERE = 62;
 // const uint32_t WARNING_MALWARE_PAGE_FRAME_IGNORE_WARNING = 63;
 
 // const uint32_t WARNING_PHISHING_PAGE_FRAME = 64;
 // const uint32_t WARNING_PHISHING_PAGE_FRAME_WHY_BLOCKED = 65;
 // const uint32_t WARNING_PHISHING_PAGE_FRAME_GET_ME_OUT_OF_HERE = 66;
 // const uint32_t WARNING_PHISHING_PAGE_FRAME_IGNORE_WARNING = 67;
 
-const uint32_t WARNING_BAD_CERT_TOP = 68;
-const uint32_t WARNING_BAD_CERT_TOP_STS = 69;
-const uint32_t WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION = 70;
+//const uint32_t WARNING_BAD_CERT_TOP = 68;
+//const uint32_t WARNING_BAD_CERT_TOP_STS = 69;
+//const uint32_t WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION = 70;
 const uint32_t WARNING_BAD_CERT_TOP_CLICK_VIEW_CERT = 71;
 const uint32_t WARNING_BAD_CERT_TOP_DONT_REMEMBER_EXCEPTION = 72;
-const uint32_t WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE = 73;
-const uint32_t WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS = 74;
+//const uint32_t WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE = 73;
+//const uint32_t WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS = 74;
 //     removed WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS = 75;
 
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_BASE = 76;
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_UNTRUSTED = 1;
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_DOMAIN = 2;
 const uint32_t WARNING_BAD_CERT_TOP_ADD_EXCEPTION_FLAG_TIME = 4;
 
 const uint32_t WARNING_BAD_CERT_TOP_CONFIRM_ADD_EXCEPTION_BASE = 84;
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -547,16 +547,63 @@ devtools.main:
     description: The amount of time a tool was opened for.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       time_open: Time open.
       os: The OS name and version e.g. "Linux 4.4.0-1014-aws", "Darwin 14.5.0", "Windows_NT 6.1.7601" or "Windows_NT 10.0.15063." This can be used to make sense of data when a feature is only available from a particular operating system build number.
       session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
 
+security.ui.certerror:
+  load:
+    objects: ["aboutcerterror"]
+    bug_numbers:
+      - 1484255
+    description: >
+      The about:certerror page is loaded, keyed by error code, see https://searchfox.org/mozilla-central/source/security/nss/lib/mozpkix/include/pkix/Result.h
+    expiry_version: "70"
+    notification_emails:
+      - jhofmann@mozilla.com
+      - seceng-telemetry@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes: ["content"]
+    products:
+      - firefox
+    extra_keys:
+      is_frame: If the error page is loaded in an iframe.
+      has_sts: If the error page is for a site with HSTS headers or with a pinned key.
+  click:
+    objects: [
+      "advanced_button",
+      "exception_button",
+      "return_button_top",
+      "return_button_adv",
+      "learn_more_link",
+      "auto_report_cb",
+      "error_code_link",
+      "clipboard_button_top",
+      "clipboard_button_bot",
+    ]
+    bug_numbers:
+      - 1484255
+    description: >
+      User interaction by click events on the cert error page. Keyed by error code, see https://searchfox.org/mozilla-central/source/security/nss/lib/mozpkix/include/pkix/Result.h
+    expiry_version: "70"
+    notification_emails:
+      - jhofmann@mozilla.com
+      - seceng-telemetry@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes: ["content"]
+    products:
+      - firefox
+    extra_keys:
+      is_frame: If the error page is loaded in an iframe.
+      has_sts: If the error page is for a site with HSTS headers or with a pinned key.
+      panel_open: If the advanced panel was open at the time of the interaction.
+
 security.ui.identitypopup:
   open:
     objects: ["identity_popup"]
     bug_numbers:
       - 1484251
     description: >
       How many times the control center was opened.
       Keyed by the state of the content blocking shield, where the shield-showing key indicates