Backout bug 1444554 and bug 1445009 by reverting affected files to previous revision.
authorXidorn Quan <me@upsuper.org>
Wed, 21 Mar 2018 12:56:30 +1100
changeset 409115 6e5043e04b88f201eeceb59165d213bc4b8b15e8
parent 409114 7db445828978147b9ab303754bc37424a04881b9
child 409116 279aeaacb2867de4ffa49f254347ac14bf6a195c
push id33675
push usertoros@mozilla.com
push dateWed, 21 Mar 2018 09:40:24 +0000
treeherdermozilla-central@f4ddf30ecf57 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1444554, 1445009
milestone61.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
Backout bug 1444554 and bug 1445009 by reverting affected files to previous revision.
browser/modules/BrowserErrorReporter.jsm
browser/modules/test/browser/browser_BrowserErrorReporter.js
toolkit/components/telemetry/Scalars.yaml
--- a/browser/modules/BrowserErrorReporter.jsm
+++ b/browser/modules/BrowserErrorReporter.jsm
@@ -19,23 +19,16 @@ const ERROR_PREFIX_RE = /^[^\W]+:/m;
 const PREF_ENABLED = "browser.chrome.errorReporter.enabled";
 const PREF_LOG_LEVEL = "browser.chrome.errorReporter.logLevel";
 const PREF_PROJECT_ID = "browser.chrome.errorReporter.projectId";
 const PREF_PUBLIC_KEY = "browser.chrome.errorReporter.publicKey";
 const PREF_SAMPLE_RATE = "browser.chrome.errorReporter.sampleRate";
 const PREF_SUBMIT_URL = "browser.chrome.errorReporter.submitUrl";
 const SDK_NAME = "firefox-error-reporter";
 const SDK_VERSION = "1.0.0";
-const TELEMETRY_ERROR_COLLECTED = "browser.errors.collected_count";
-const TELEMETRY_ERROR_COLLECTED_FILENAME = "browser.errors.collected_count_by_filename";
-const TELEMETRY_ERROR_COLLECTED_STACK = "browser.errors.collected_with_stack_count";
-const TELEMETRY_ERROR_REPORTED = "browser.errors.reported_success_count";
-const TELEMETRY_ERROR_REPORTED_FAIL = "browser.errors.reported_failure_count";
-const TELEMETRY_ERROR_SAMPLE_RATE = "browser.errors.sample_rate";
-
 
 // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIScriptError#Categories
 const REPORTED_CATEGORIES = new Set([
   "XPConnect JavaScript",
   "component javascript",
   "chrome javascript",
   "chrome registration",
   "XBL",
@@ -47,23 +40,16 @@ const REPORTED_CATEGORIES = new Set([
 
 const PLATFORM_NAMES = {
   linux: "Linux",
   win: "Windows",
   macosx: "macOS",
   android: "Android",
 };
 
-// Filename URI regexes that we are okay with reporting to Telemetry. URIs not
-// matching these patterns may contain local file paths.
-const TELEMETRY_REPORTED_PATTERNS = new Set([
-  /^resource:\/\/(?:\/|gre)/,
-  /^chrome:\/\/(?:global|browser|devtools)/,
-]);
-
 /**
  * Collects nsIScriptError messages logged to the browser console and reports
  * them to a remotely-hosted error collection service.
  *
  * This is a PROTOTYPE; it will be removed in the future and potentially
  * replaced with a more robust implementation. It is meant to only collect
  * errors from Nightly (and local builds if enabled for development purposes)
  * and has not been reviewed for use outside of Nightly.
@@ -71,26 +57,20 @@ const TELEMETRY_REPORTED_PATTERNS = new 
  * The outgoing requests are designed to be compatible with Sentry. See
  * https://docs.sentry.io/clientdev/ for details on the data format that Sentry
  * expects.
  *
  * Errors may contain PII, such as in messages or local file paths in stack
  * traces; see bug 1426482 for privacy review and server-side mitigation.
  */
 class BrowserErrorReporter {
-  constructor(options = {}) {
+  constructor(fetchMethod = this._defaultFetch, chromeOnly = true) {
     // Test arguments for mocks and changing behavior
-    this.fetch = options.fetch || defaultFetch;
-    this.chromeOnly = options.chromeOnly !== undefined ? options.chromeOnly : true;
-    this.registerListener = (
-      options.registerListener || (() => Services.console.registerListener(this))
-    );
-    this.unregisterListener = (
-      options.unregisterListener || (() => Services.console.unregisterListener(this))
-    );
+    this.fetch = fetchMethod;
+    this.chromeOnly = chromeOnly;
 
     // Values that don't change between error reports.
     this.requestBodyTemplate = {
       logger: "javascript",
       platform: "javascript",
       release: Services.appinfo.version,
       environment: UpdateUtils.getUpdateChannel(false),
       contexts: {
@@ -119,129 +99,131 @@ class BrowserErrorReporter {
 
     XPCOMUtils.defineLazyPreferenceGetter(
       this,
       "collectionEnabled",
       PREF_ENABLED,
       false,
       this.handleEnabledPrefChanged.bind(this),
     );
-    XPCOMUtils.defineLazyPreferenceGetter(
-      this,
-      "sampleRatePref",
-      PREF_SAMPLE_RATE,
-      "0.0",
-      this.handleSampleRatePrefChanged.bind(this),
-    );
   }
 
   /**
    * Lazily-created logger
    */
   get logger() {
     const logger = Log.repository.getLogger("BrowserErrorReporter");
     logger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
     logger.manageLevelFromPref(PREF_LOG_LEVEL);
 
     Object.defineProperty(this, "logger", {value: logger});
     return this.logger;
   }
 
   init() {
     if (this.collectionEnabled) {
-      this.registerListener();
+      Services.console.registerListener(this);
 
       // Processing already-logged messages in case any errors occurred before
       // startup.
       for (const message of Services.console.getMessageArray()) {
         this.observe(message);
       }
     }
   }
 
   uninit() {
     try {
-      this.unregisterListener();
+      Services.console.unregisterListener(this);
     } catch (err) {} // It probably wasn't registered.
   }
 
   handleEnabledPrefChanged(prefName, previousValue, newValue) {
     if (newValue) {
-      this.registerListener();
+      Services.console.registerListener(this);
     } else {
       try {
-        this.unregisterListener();
+        Services.console.unregisterListener(this);
       } catch (err) {} // It probably wasn't registered.
     }
   }
 
-  handleSampleRatePrefChanged(prefName, previousValue, newValue) {
-    Services.telemetry.scalarSet(TELEMETRY_ERROR_SAMPLE_RATE, newValue);
-  }
-
-  shouldReportFilename(filename) {
-    for (const pattern of TELEMETRY_REPORTED_PATTERNS) {
-      if (filename.match(pattern)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   async observe(message) {
     try {
       message.QueryInterface(Ci.nsIScriptError);
     } catch (err) {
       return; // Not an error
     }
 
     const isWarning = message.flags & message.warningFlag;
     const isFromChrome = REPORTED_CATEGORIES.has(message.category);
     if ((this.chromeOnly && !isFromChrome) || isWarning) {
       return;
     }
 
-    // Record that we collected an error prior to applying the sample rate
-    Services.telemetry.scalarAdd(TELEMETRY_ERROR_COLLECTED, 1);
-    if (message.stack) {
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_COLLECTED_STACK, 1);
-    }
-    if (message.sourceName) {
-      let filename = "FILTERED";
-      if (this.shouldReportFilename(message.sourceName)) {
-        filename = message.sourceName;
-      }
-      Services.telemetry.keyedScalarAdd(TELEMETRY_ERROR_COLLECTED_FILENAME, filename.slice(0, 69), 1);
-    }
-
     // Sample the amount of errors we send out
-    const sampleRate = Number.parseFloat(this.sampleRatePref);
+    const sampleRate = Number.parseFloat(Services.prefs.getCharPref(PREF_SAMPLE_RATE));
     if (!Number.isFinite(sampleRate) || (Math.random() >= sampleRate)) {
       return;
     }
 
-     merge rev
+    const extensions = new Map();
+    for (let extension of WebExtensionPolicy.getActiveExtensions()) {
+      extensions.set(extension.mozExtensionHostname, extension);
+    }
+
+    // Replaces any instances of moz-extension:// URLs with internal UUIDs to use
+    // the add-on ID instead.
+    function mangleExtURL(string, anchored = true) {
+      let re = new RegExp(`${anchored ? "^" : ""}moz-extension://([^/]+)/`, "g");
+
+      return string.replace(re, (m0, m1) => {
+        let id = extensions.has(m1) ? extensions.get(m1).id : m1;
+        return `moz-extension://${id}/`;
+      });
+    }
+
+    // Parse the error type from the message if present (e.g. "TypeError: Whoops").
+    let errorMessage = message.errorMessage;
+    let errorName = "Error";
+    if (message.errorMessage.match(ERROR_PREFIX_RE)) {
+      const parts = message.errorMessage.split(":");
+      errorName = parts[0];
+      errorMessage = parts.slice(1).join(":").trim();
+    }
+
+    const frames = [];
+    let frame = message.stack;
+    // Avoid an infinite loop by limiting traces to 100 frames.
+    while (frame && frames.length < 100) {
+      const normalizedFrame = await this.normalizeStackFrame(frame);
+      normalizedFrame.module = mangleExtURL(normalizedFrame.module, false);
+      frames.push(normalizedFrame);
+      frame = frame.parent;
+    }
+    // Frames are sent in order from oldest to newest.
+    frames.reverse();
+
+    const requestBody = Object.assign({}, this.requestBodyTemplate, {
       timestamp: new Date().toISOString().slice(0, -1), // Remove trailing "Z"
       project: Services.prefs.getCharPref(PREF_PROJECT_ID),
       exception: {
-        values: [exceptionValue],
+        values: [
+          {
+            type: errorName,
+            value: mangleExtURL(errorMessage),
+            module: message.sourceName,
+            stacktrace: {
+              frames,
+            }
+          },
+        ],
       },
-      tags: {},
-    };
-
-    const transforms = [
-      addErrorMessage,
-      addStacktrace,
-      addModule,
-      mangleExtensionUrls,
-      tagExtensionErrors,
-    ];
-    for (const transform of transforms) {
-      await transform(message, exceptionValue, requestBody);
-    }
+      culprit: message.sourceName,
+    });
 
     const url = new URL(Services.prefs.getCharPref(PREF_SUBMIT_URL));
     url.searchParams.set("sentry_client", `${SDK_NAME}/${SDK_VERSION}`);
     url.searchParams.set("sentry_version", "7");
     url.searchParams.set("sentry_key", Services.prefs.getCharPref(PREF_PUBLIC_KEY));
 
     try {
       await this.fetch(url, {
@@ -249,53 +231,23 @@ class BrowserErrorReporter {
         headers: {
           "Content-Type": "application/json",
           "Accept": "application/json",
         },
         // Sentry throws an auth error without a referrer specified.
         referrer: "https://fake.mozilla.org",
         body: JSON.stringify(requestBody)
       });
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_REPORTED, 1);
       this.logger.debug("Sent error successfully.");
     } catch (error) {
-      Services.telemetry.scalarAdd(TELEMETRY_ERROR_REPORTED_FAIL, 1);
       this.logger.warn(`Failed to send error: ${error}`);
     }
   }
-}
 
-function defaultFetch(...args) {
-  // Do not make network requests while running in automation
-  if (Cu.isInAutomation) {
-    return null;
-  }
-
-  return fetch(...args);
-}
-
-function addErrorMessage(message, exceptionValue) {
-  // Parse the error type from the message if present (e.g. "TypeError: Whoops").
-  let errorMessage = message.errorMessage;
-  let errorName = "Error";
-  if (message.errorMessage.match(ERROR_PREFIX_RE)) {
-    const parts = message.errorMessage.split(":");
-    errorName = parts[0];
-    errorMessage = parts.slice(1).join(":").trim();
-  }
-
-  exceptionValue.type = errorName;
-  exceptionValue.value = errorMessage;
-}
-
-async function addStacktrace(message, exceptionValue) {
-  const frames = [];
-  let frame = message.stack;
-  // Avoid an infinite loop by limiting traces to 100 frames.
-  while (frame && frames.length < 100) {
+  async normalizeStackFrame(frame) {
     const normalizedFrame = {
       function: frame.functionDisplayName,
       module: frame.source,
       lineno: frame.line,
       colno: frame.column,
     };
 
     try {
@@ -320,54 +272,20 @@ async function addStacktrace(message, ex
         lineIndex + 1,
         Math.min(lineIndex + 1 + CONTEXT_LINES, sourceLines.length),
       );
     } catch (err) {
       // Could be a fetch issue, could be a line index issue. Not much we can
       // do to recover in either case.
     }
 
-    frames.push(normalizedFrame);
-    frame = frame.parent;
-  }
-  // Frames are sent in order from oldest to newest.
-  frames.reverse();
-
-  exceptionValue.stacktrace = {frames};
-}
-
-function addModule(message, exceptionValue) {
-  exceptionValue.module = message.sourceName;
-}
-
-function mangleExtensionUrls(message, exceptionValue) {
-  const extensions = new Map();
-  for (let extension of WebExtensionPolicy.getActiveExtensions()) {
-    extensions.set(extension.mozExtensionHostname, extension);
+    return normalizedFrame;
   }
 
-  // Replaces any instances of moz-extension:// URLs with internal UUIDs to use
-  // the add-on ID instead.
-  function mangleExtURL(string, anchored = true) {
-    if (!string) {
-      return string;
+  async _defaultFetch(...args) {
+    // Do not make network requests while running in automation
+    if (Cu.isInAutomation) {
+      return null;
     }
 
-    let re = new RegExp(`${anchored ? "^" : ""}moz-extension://([^/]+)/`, "g");
-
-    return string.replace(re, (m0, m1) => {
-      let id = extensions.has(m1) ? extensions.get(m1).id : m1;
-      return `moz-extension://${id}/`;
-    });
-  }
-
-  exceptionValue.value = mangleExtURL(exceptionValue.value, false);
-  exceptionValue.module = mangleExtURL(exceptionValue.module);
-  for (const frame of exceptionValue.stacktrace.frames) {
-    frame.module = mangleExtURL(frame.module);
+    return fetch(...args);
   }
 }
-
-function tagExtensionErrors(message, exceptionValue, requestBody) {
-  if (exceptionValue.module && exceptionValue.module.startsWith("moz-extension://")) {
-    requestBody.tags.isExtensionError = true;
-  }
-}
--- a/browser/modules/test/browser/browser_BrowserErrorReporter.js
+++ b/browser/modules/test/browser/browser_BrowserErrorReporter.js
@@ -11,48 +11,69 @@ registerCleanupFunction(function() {
   delete window.sinon;
 });
 
 const PREF_ENABLED = "browser.chrome.errorReporter.enabled";
 const PREF_PROJECT_ID = "browser.chrome.errorReporter.projectId";
 const PREF_PUBLIC_KEY = "browser.chrome.errorReporter.publicKey";
 const PREF_SAMPLE_RATE = "browser.chrome.errorReporter.sampleRate";
 const PREF_SUBMIT_URL = "browser.chrome.errorReporter.submitUrl";
-const TELEMETRY_ERROR_COLLECTED = "browser.errors.collected_count";
-const TELEMETRY_ERROR_COLLECTED_FILENAME = "browser.errors.collected_count_by_filename";
-const TELEMETRY_ERROR_COLLECTED_STACK = "browser.errors.collected_with_stack_count";
-const TELEMETRY_ERROR_REPORTED = "browser.errors.reported_success_count";
-const TELEMETRY_ERROR_REPORTED_FAIL = "browser.errors.reported_failure_count";
-const TELEMETRY_ERROR_SAMPLE_RATE = "browser.errors.sample_rate";
 
 function createScriptError(options = {}) {
   const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
   scriptError.init(
     options.message || "",
     options.sourceName || null,
     options.sourceLine || null,
     options.lineNumber || null,
     options.columnNumber || null,
     options.flags || Ci.nsIScriptError.errorFlag,
     options.category || "chrome javascript",
   );
   return scriptError;
 }
 
-function noop() {
-  // Do nothing
+// Wrapper around Services.console.logMessage that waits for the message to be
+// logged before resolving, since messages are logged asynchronously.
+function logMessage(message) {
+  return new Promise(resolve => {
+    Services.console.registerListener({
+      observe(loggedMessage) {
+        if (loggedMessage.message === message.message) {
+          Services.console.unregisterListener(this);
+          resolve();
+        }
+      },
+    });
+    Services.console.logMessage(message);
+  });
 }
 
 // Clears the console of any previous messages. Should be called at the end of
 // each test that logs to the console.
 function resetConsole() {
   Services.console.logStringMessage("");
   Services.console.reset();
 }
 
+// Wrapper similar to logMessage, but for logStringMessage.
+function logStringMessage(message) {
+  return new Promise(resolve => {
+    Services.console.registerListener({
+      observe(loggedMessage) {
+        if (loggedMessage.message === message) {
+          Services.console.unregisterListener(this);
+          resolve();
+        }
+      },
+    });
+    Services.console.logStringMessage(message);
+  });
+}
+
 // Finds the fetch spy call for an error with a matching message.
 function fetchCallForMessage(fetchSpy, message) {
   for (const call of fetchSpy.getCalls()) {
     const body = JSON.parse(call.args[1].body);
     if (body.exception.values[0].value.includes(message)) {
       return call;
     }
   }
@@ -61,242 +82,257 @@ function fetchCallForMessage(fetchSpy, m
 }
 
 // Helper to test if a fetch spy was called with the given error message.
 // Used in tests where unrelated JS errors from other code are logged.
 function fetchPassedError(fetchSpy, message) {
   return fetchCallForMessage(fetchSpy, message) !== null;
 }
 
-add_task(async function testSetup() {
-  const canRecordExtended = Services.telemetry.canRecordExtended;
-  Services.telemetry.canRecordExtended = true;
-  registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended);
-});
-
 add_task(async function testInitPrefDisabled() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-  });
+  const fetchSpy = sinon.spy();
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, false],
+    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  ok(!listening, "Reporter does not listen for errors if the enabled pref is false.");
+  await logMessage(createScriptError({message: "Logged while disabled"}));
+  ok(
+    !fetchPassedError(fetchSpy, "Logged while disabled"),
+    "Reporter does not listen for errors if the enabled pref is false.",
+  );
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testInitUninitPrefEnabled() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-    unregisterListener() {
-      listening = false;
-    },
-  });
+  const fetchSpy = sinon.spy();
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
+    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  ok(listening, "Reporter listens for errors if the enabled pref is true.");
+  await logMessage(createScriptError({message: "Logged after init"}));
+  ok(
+    fetchPassedError(fetchSpy, "Logged after init"),
+    "Reporter listens for errors if the enabled pref is true.",
+  );
 
+  fetchSpy.reset();
+  ok(!fetchSpy.called, "Fetch spy was reset.");
   reporter.uninit();
-  ok(!listening, "Reporter does not listen for errors after uninit.");
+  await logMessage(createScriptError({message: "Logged after uninit"}));
+  ok(
+    !fetchPassedError(fetchSpy, "Logged after uninit"),
+    "Reporter does not listen for errors after uninit.",
+  );
+
+  resetConsole();
 });
 
 add_task(async function testInitPastMessages() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({
-    fetch: fetchSpy,
-    registerListener: noop,
-    unregisterListener: noop,
-  });
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  resetConsole();
-  Services.console.logMessage(createScriptError({message: "Logged before init"}));
+  await logMessage(createScriptError({message: "Logged before init"}));
   reporter.init();
-
-  // Include ok() to satisfy mochitest warning for test without any assertions
-  const errorWasLogged = await TestUtils.waitForCondition(
-    () => fetchPassedError(fetchSpy, "Logged before init"),
-    "Waiting for message to be logged",
+  ok(
+    fetchPassedError(fetchSpy, "Logged before init"),
+    "Reporter collects errors logged before initialization.",
   );
-  ok(errorWasLogged, "Reporter collects errors logged before initialization.");
-
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testEnabledPrefWatcher() {
-  let listening = false;
-  const reporter = new BrowserErrorReporter({
-    registerListener() {
-      listening = true;
-    },
-    unregisterListener() {
-      listening = false;
-    },
-  });
+  const fetchSpy = sinon.spy();
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, false],
+    [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
   reporter.init();
-  ok(!listening, "Reporter does not collect errors if the enable pref is false.");
+  await logMessage(createScriptError({message: "Shouldn't report"}));
+  ok(
+    !fetchPassedError(fetchSpy, "Shouldn't report"),
+    "Reporter does not collect errors if the enable pref is false.",
+  );
 
-  Services.console.logMessage(createScriptError({message: "Shouldn't report"}));
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
   ]});
-  ok(listening, "Reporter collects errors if the enabled pref switches to true.");
+  ok(
+    !fetchPassedError(fetchSpy, "Shouldn't report"),
+    "Reporter does not collect past-logged errors if it is enabled mid-run.",
+  );
+  await logMessage(createScriptError({message: "Should report"}));
+  ok(
+    fetchPassedError(fetchSpy, "Should report"),
+    "Reporter collects errors logged after the enabled pref is turned on mid-run",
+  );
+
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testNonErrorLogs() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  reporter.observe({message: "Not a scripterror instance."});
+  reporter.init();
+
+  await logStringMessage("Not a scripterror instance.");
   ok(
     !fetchPassedError(fetchSpy, "Not a scripterror instance."),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await reporter.observe(createScriptError({
+  await logMessage(createScriptError({
     message: "Warning message",
     flags: Ci.nsIScriptError.warningFlag,
   }));
   ok(
     !fetchPassedError(fetchSpy, "Warning message"),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await reporter.observe(createScriptError({
+  await logMessage(createScriptError({
     message: "Non-chrome category",
     category: "totally from a website",
   }));
   ok(
     !fetchPassedError(fetchSpy, "Non-chrome category"),
     "Reporter does not collect normal log messages or warnings.",
   );
 
-  await reporter.observe(createScriptError({message: "Is error"}));
+  await logMessage(createScriptError({message: "Is error"}));
   ok(
     fetchPassedError(fetchSpy, "Is error"),
     "Reporter collects error messages.",
   );
+
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testSampling() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  await reporter.observe(createScriptError({message: "Should log"}));
+  reporter.init();
+  await logMessage(createScriptError({message: "Should log"}));
   ok(
     fetchPassedError(fetchSpy, "Should log"),
     "A 1.0 sample rate will cause the reporter to always collect errors.",
   );
 
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_SAMPLE_RATE, "0.0"],
   ]});
-  await reporter.observe(createScriptError({message: "Shouldn't log"}));
+  await logMessage(createScriptError({message: "Shouldn't log"}));
   ok(
     !fetchPassedError(fetchSpy, "Shouldn't log"),
     "A 0.0 sample rate will cause the reporter to never collect errors.",
   );
 
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_SAMPLE_RATE, ")fasdf"],
   ]});
-  await reporter.observe(createScriptError({message: "Also shouldn't log"}));
+  await logMessage(createScriptError({message: "Also shouldn't log"}));
   ok(
     !fetchPassedError(fetchSpy, "Also shouldn't log"),
     "An invalid sample rate will cause the reporter to never collect errors.",
   );
+
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testNameMessage() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
 
-  await reporter.observe(createScriptError({message: "No name"}));
+  reporter.init();
+  await logMessage(createScriptError({message: "No name"}));
   let call = fetchCallForMessage(fetchSpy, "No name");
   let body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "Error",
     "Reporter uses a generic type when no name is in the message.",
   );
   is(
     body.exception.values[0].value,
     "No name",
     "Reporter uses error message as the exception value.",
   );
 
-  await reporter.observe(createScriptError({message: "FooError: Has name"}));
+  await logMessage(createScriptError({message: "FooError: Has name"}));
   call = fetchCallForMessage(fetchSpy, "Has name");
   body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "FooError",
     "Reporter uses the error type from the message.",
   );
   is(
     body.exception.values[0].value,
     "Has name",
     "Reporter uses error message as the value parameter.",
   );
 
-  await reporter.observe(createScriptError({message: "FooError: Has :extra: colons"}));
+  await logMessage(createScriptError({message: "FooError: Has :extra: colons"}));
   call = fetchCallForMessage(fetchSpy, "Has :extra: colons");
   body = JSON.parse(call.args[1].body);
   is(
     body.exception.values[0].type,
     "FooError",
     "Reporter uses the error type from the message.",
   );
   is(
     body.exception.values[0].value,
     "Has :extra: colons",
     "Reporter uses error message as the value parameter.",
   );
+  reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testFetchArguments() {
   const fetchSpy = sinon.spy();
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy});
+  const reporter = new BrowserErrorReporter(fetchSpy);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
     [PREF_PROJECT_ID, "123"],
     [PREF_PUBLIC_KEY, "foobar"],
     [PREF_SUBMIT_URL, "https://errors.example.com/api/123/store/"],
   ]});
 
-  resetConsole();
   reporter.init();
   const testPageUrl = (
     "chrome://mochitests/content/browser/browser/modules/test/browser/" +
     "browser_BrowserErrorReporter.html"
   );
 
   SimpleTest.expectUncaughtException();
   await BrowserTestUtils.withNewTab(testPageUrl, async () => {
@@ -364,28 +400,28 @@ add_task(async function testFetchArgumen
           },
         ],
       },
       "Reporter builds stack trace from scriptError correctly.",
     );
   });
 
   reporter.uninit();
+  resetConsole();
 });
 
 add_task(async function testAddonIDMangle() {
   const fetchSpy = sinon.spy();
   // Passing false here disables category checks on errors, which would
   // otherwise block errors directly from extensions.
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy, chromeOnly: false});
+  const reporter = new BrowserErrorReporter(fetchSpy, false);
   await SpecialPowers.pushPrefEnv({set: [
     [PREF_ENABLED, true],
     [PREF_SAMPLE_RATE, "1.0"],
   ]});
-  resetConsole();
   reporter.init();
 
   // Create and install test add-on
   const id = "browsererrorcollection@example.com";
   const extension = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: { id },
@@ -406,187 +442,10 @@ add_task(async function testAddonIDMangl
   const stackFrame = body.exception.values[0].stacktrace.frames[0];
   ok(
     stackFrame.module.startsWith(`moz-extension://${id}/`),
     "Stack frame filenames use the proper add-on ID instead of internal UUIDs.",
   );
 
   await extension.unload();
   reporter.uninit();
-});
-
-add_task(async function testExtensionTag() {
-  const fetchSpy = sinon.spy();
-  // Passing false here disables category checks on errors, which would
-  // otherwise block errors directly from extensions.
-  const reporter = new BrowserErrorReporter({fetch: fetchSpy, chromeOnly: false});
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-  resetConsole();
-  reporter.init();
-
-  // Create and install test add-on
-  const id = "browsererrorcollection@example.com";
-  const extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      applications: {
-        gecko: { id },
-      },
-    },
-    background() {
-      throw new Error("testExtensionTag error");
-    },
-  });
-  await extension.startup();
-
-  // Just in case the error hasn't been thrown before add-on startup.
-  let call = await TestUtils.waitForCondition(
-    () => fetchCallForMessage(fetchSpy, "testExtensionTag error"),
-    `Wait for error from ${id} to be logged`,
-  );
-  let body = JSON.parse(call.args[1].body);
-  ok(body.tags.isExtensionError, "Errors from extensions have an isExtensionError tag.");
-
-  await extension.unload();
-  reporter.uninit();
-
-  await reporter.observe(createScriptError({message: "testExtensionTag not from extension"}));
-  call = fetchCallForMessage(fetchSpy, "testExtensionTag not from extension");
-  body = JSON.parse(call.args[1].body);
-  is(body.tags.isExtensionError, undefined, "Normal errors do not have an isExtensionError tag.");
-});
-
-add_task(async function testScalars() {
-  const fetchStub = sinon.stub();
-  const reporter = new BrowserErrorReporter(fetchStub);
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  Services.telemetry.clearScalars();
-
-  const messages = [
-    createScriptError({message: "No name"}),
-    createScriptError({message: "Also no name", sourceName: "resource://gre/modules/Foo.jsm"}),
-    createScriptError({message: "More no name", sourceName: "resource://gre/modules/Bar.jsm"}),
-    createScriptError({message: "Yeah sures", sourceName: "unsafe://gre/modules/Bar.jsm"}),
-    createScriptError({
-      message: "long",
-      sourceName: "resource://gre/modules/long/long/long/long/long/long/long/long/long/long/",
-    }),
-    {message: "Not a scripterror instance."},
-
-    // No easy way to create an nsIScriptError with a stack, so let's pretend.
-    Object.create(
-      createScriptError({message: "Whatever"}),
-      {stack: {value: new Error().stack}},
-    ),
-  ];
-
-  // Use observe to avoid errors from other code messing up our counts.
-  for (const message of messages) {
-    await reporter.observe(message);
-  }
-
-  await SpecialPowers.pushPrefEnv({set: [[PREF_SAMPLE_RATE, "0.0"]]});
-  await reporter.observe(createScriptError({message: "Additionally no name"}));
-
-  await SpecialPowers.pushPrefEnv({set: [[PREF_SAMPLE_RATE, "1.0"]]});
-  fetchStub.throws(new Error("Could not report"));
-  await reporter.observe(createScriptError({message: "Maybe name?"}));
-
-  const optin = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
-  const scalars = Services.telemetry.snapshotScalars(optin, false).parent;
-  is(
-    scalars[TELEMETRY_ERROR_COLLECTED],
-    8,
-    `${TELEMETRY_ERROR_COLLECTED} is incremented when an error is collected.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_SAMPLE_RATE],
-    "1.0",
-    `${TELEMETRY_ERROR_SAMPLE_RATE} contains the last sample rate used.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_REPORTED],
-    6,
-    `${TELEMETRY_ERROR_REPORTED} is incremented when an error is reported.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_REPORTED_FAIL],
-    1,
-    `${TELEMETRY_ERROR_REPORTED_FAIL} is incremented when an error fails to be reported.`,
-  );
-  is(
-    scalars[TELEMETRY_ERROR_COLLECTED_STACK],
-    1,
-    `${TELEMETRY_ERROR_REPORTED_FAIL} is incremented when an error with a stack trace is collected.`,
-  );
-
-  const keyedScalars = Services.telemetry.snapshotKeyedScalars(optin, false).parent;
-  Assert.deepEqual(
-    keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME],
-    {
-      "FILTERED": 1,
-      "resource://gre/modules/Foo.jsm": 1,
-      "resource://gre/modules/Bar.jsm": 1,
-      // Cut off at 70-character limit
-      "resource://gre/modules/long/long/long/long/long/long/long/long/long/l": 1,
-    },
-    `${TELEMETRY_ERROR_COLLECTED_FILENAME} is incremented when an error is collected.`,
-  );
-
   resetConsole();
 });
-
-add_task(async function testCollectedFilenameScalar() {
-  const fetchStub = sinon.stub();
-  const reporter = new BrowserErrorReporter(fetchStub);
-  await SpecialPowers.pushPrefEnv({set: [
-    [PREF_ENABLED, true],
-    [PREF_SAMPLE_RATE, "1.0"],
-  ]});
-
-  const testCases = [
-    ["chrome://unknown/module.jsm", false],
-    ["resource://unknown/module.jsm", false],
-    ["unknown://unknown/module.jsm", false],
-
-    ["resource://gre/modules/Foo.jsm", true],
-    ["resource:///modules/Foo.jsm", true],
-    ["chrome://global/Foo.jsm", true],
-    ["chrome://browser/Foo.jsm", true],
-    ["chrome://devtools/Foo.jsm", true],
-  ];
-
-  for (const [filename, shouldMatch] of testCases) {
-    Services.telemetry.clearScalars();
-
-    // Use observe to avoid errors from other code messing up our counts.
-    await reporter.observe(createScriptError({
-      message: "Fine",
-      sourceName: filename,
-    }));
-
-    const keyedScalars = (
-      Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false).parent
-    );
-
-    let matched = null;
-    if (shouldMatch) {
-      matched = keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME][filename] === 1;
-    } else {
-      matched = keyedScalars[TELEMETRY_ERROR_COLLECTED_FILENAME].FILTERED === 1;
-    }
-
-    ok(
-      matched,
-      shouldMatch
-        ? `${TELEMETRY_ERROR_COLLECTED_FILENAME} logs a key for ${filename}.`
-        : `${TELEMETRY_ERROR_COLLECTED_FILENAME} logs a FILTERED key for ${filename}.`,
-    );
-  }
-
-  resetConsole();
-});
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -1486,104 +1486,16 @@ sw:
     notification_emails:
       - sw-telemetry@mozilla.com
       - echuang@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - 'main'
       - 'content'
 
-# The following section contains the BrowserErrorReporter scalars.
-browser.errors:
-  collected_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were collected locally.
-    expires: "64"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  collected_with_stack_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of browser chrome JS errors that were collected locally and had
-      a usable stack trace.
-    expires: "64"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  reported_success_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were reported to the
-      remote collection service.
-    expires: "64"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  reported_failure_count:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that we attempted to report to
-      the remote collection service, but failed to.
-    expires: "64"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  sample_rate:
-    bug_numbers:
-      - 1444554
-    description: >
-      The sample rate at which collected errors were reported.
-    expires: "64"
-    kind: string
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
-  collected_count_by_filename:
-    bug_numbers:
-      - 1444554
-    description: >
-      The count of all browser chrome JS errors that were collected locally,
-      keyed by the filename of the file in which the error occurred. Collected
-      filenames are limited to specific paths under the resource:// and
-      chrome:// protocols; non-matching filenames are reported as "FILTERED".
-      Long filenames are truncated to the first 70 characters.
-    keyed: true
-    expires: "64"
-    kind: uint
-    notification_emails:
-      - nightly-js-errors@mozilla.com
-      - mkelly@mozilla.com
-    record_in_processes:
-      - 'main'
-
 # The following section is for probes testing the Telemetry system. They will not be
 # submitted in pings and are only used for testing.
 telemetry.test:
   unsigned_int_kind:
     bug_numbers:
       - 1276190
     description: >
       This is a test uint type with a really long description, maybe spanning even multiple