Bug 1234526 - Remove services/healthreport. r=gfritzsche
☠☠ backed out by 19a2342819e4 ☠ ☠
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Wed, 06 Jan 2016 09:07:00 +0100
changeset 320137 096d46bdaf86112bc1bf350fec18d5d4063ab0c2
parent 320136 96e0326e798579fda3e4babf0a253b43e19e2635
child 320138 3124025d114712f6717e6cf6a4ba930f0ab02ac3
push id9143
push userahunt@mozilla.com
push dateFri, 08 Jan 2016 21:30:53 +0000
reviewersgfritzsche
bugs1234526
milestone46.0a1
Bug 1234526 - Remove services/healthreport. r=gfritzsche
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
mobile/android/b2gdroid/installer/package-manifest.in
modules/libpref/greprefs.js
services/healthreport/HealthReport.jsm
services/healthreport/HealthReportComponents.manifest
services/healthreport/docs/architecture.rst
services/healthreport/docs/dataformat.rst
services/healthreport/docs/identifiers.rst
services/healthreport/docs/index.rst
services/healthreport/healthreporter.jsm
services/healthreport/modules-testing/utils.jsm
services/healthreport/moz.build
services/healthreport/profile.jsm
services/healthreport/providers.jsm
services/healthreport/tests/xpcshell/head.js
services/healthreport/tests/xpcshell/test_load_modules.js
services/healthreport/tests/xpcshell/test_profile.js
services/healthreport/tests/xpcshell/test_provider_addons.js
services/healthreport/tests/xpcshell/test_provider_appinfo.js
services/healthreport/tests/xpcshell/test_provider_crashes.js
services/healthreport/tests/xpcshell/test_provider_hotfix.js
services/healthreport/tests/xpcshell/test_provider_places.js
services/healthreport/tests/xpcshell/test_provider_searches.js
services/healthreport/tests/xpcshell/test_provider_sessions.js
services/healthreport/tests/xpcshell/test_provider_sysinfo.js
services/healthreport/tests/xpcshell/xpcshell.ini
services/moz.build
toolkit/components/telemetry/docs/fhr/architecture.rst
toolkit/components/telemetry/docs/fhr/dataformat.rst
toolkit/components/telemetry/docs/fhr/identifiers.rst
toolkit/components/telemetry/docs/fhr/index.rst
toolkit/components/telemetry/moz.build
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -626,20 +626,16 @@
 #ifdef MOZ_SERVICES_SYNC
 @RESPATH@/components/SyncComponents.manifest
 @RESPATH@/components/Weave.js
 @RESPATH@/components/WeaveCrypto.manifest
 @RESPATH@/components/WeaveCrypto.js
 #endif
 @RESPATH@/components/servicesComponents.manifest
 @RESPATH@/components/cryptoComponents.manifest
-#ifdef MOZ_SERVICES_HEALTHREPORT
-@RESPATH@/components/HealthReportComponents.manifest
-@RESPATH@/components/HealthReportService.js
-#endif
 @RESPATH@/components/CaptivePortalDetectComponents.manifest
 @RESPATH@/components/captivedetect.js
 @RESPATH@/components/TelemetryStartup.js
 @RESPATH@/components/TelemetryStartup.manifest
 @RESPATH@/components/XULStore.js
 @RESPATH@/components/XULStore.manifest
 @RESPATH@/components/Webapps.js
 @RESPATH@/components/Webapps.manifest
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -497,17 +497,16 @@
 #ifdef XP_MACOSX
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
 @RESPATH@/components/nsINIProcessor.manifest
 @RESPATH@/components/nsINIProcessor.js
 @RESPATH@/components/nsPrompter.manifest
 @RESPATH@/components/nsPrompter.js
 #ifdef MOZ_SERVICES_HEALTHREPORT
-@RESPATH@/components/HealthReportComponents.manifest
 @RESPATH@/browser/components/SelfSupportService.manifest
 @RESPATH@/browser/components/SelfSupportService.js
 #endif
 @RESPATH@/components/SyncComponents.manifest
 @RESPATH@/components/Weave.js
 @RESPATH@/components/CaptivePortalDetectComponents.manifest
 @RESPATH@/components/captivedetect.js
 @RESPATH@/components/servicesComponents.manifest
--- a/mobile/android/b2gdroid/installer/package-manifest.in
+++ b/mobile/android/b2gdroid/installer/package-manifest.in
@@ -427,21 +427,16 @@
 @BINPATH@/components/ActivityWrapper.js
 @BINPATH@/components/ActivityMessageConfigurator.js
 
 #ifdef MOZ_WEBRTC
 @BINPATH@/components/PeerConnection.js
 @BINPATH@/components/PeerConnection.manifest
 #endif
 
-#ifdef MOZ_SERVICES_HEALTHREPORT
-@BINPATH@/components/HealthReportComponents.manifest
-@BINPATH@/components/HealthReportService.js
-#endif
-
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
 
 #ifdef MOZ_DEBUG
--- a/modules/libpref/greprefs.js
+++ b/modules/libpref/greprefs.js
@@ -2,11 +2,11 @@
 #include init/all.js
 #ifdef MOZ_DATA_REPORTING
 #include ../../toolkit/components/telemetry/datareporting-prefs.js
 #endif
 #ifdef MOZ_SERVICES_HEALTHREPORT
 #if MOZ_WIDGET_TOOLKIT == android
 #include ../../mobile/android/chrome/content/healthreport-prefs.js
 #else
-#include ../../services/healthreport/healthreport-prefs.js
+#include ../../toolkit/components/telemetry/healthreport-prefs.js
 #endif
 #endif
deleted file mode 100644
--- a/services/healthreport/HealthReport.jsm
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "HealthReporter",
-  "AddonsProvider",
-  "AppInfoProvider",
-  "CrashesProvider",
-  "HealthReportProvider",
-  "HotfixProvider",
-  "Metrics",
-  "PlacesProvider",
-  "ProfileMetadataProvider",
-  "SearchesProvider",
-  "SessionsProvider",
-  "SysInfoProvider",
-];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
-
-// We concatenate the JSMs together to eliminate compartment overhead.
-// This is a giant hack until compartment overhead is no longer an
-// issue.
-#define MERGED_COMPARTMENT
-
-#include ../common/async.js
-;
-#include ../common/bagheeraclient.js
-;
-#include ../metrics/Metrics.jsm
-;
-#include healthreporter.jsm
-;
-#include profile.jsm
-;
-#include providers.jsm
-;
-
deleted file mode 100644
--- a/services/healthreport/HealthReportComponents.manifest
+++ /dev/null
@@ -1,16 +0,0 @@
-# Register Firefox Health Report providers.
-category healthreport-js-provider-default AddonsProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default AppInfoProvider resource://gre/modules/HealthReport.jsm
-#ifdef MOZ_CRASHREPORTER
-category healthreport-js-provider-default CrashesProvider resource://gre/modules/HealthReport.jsm
-#endif
-category healthreport-js-provider-default HealthReportProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default HotfixProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default PlacesProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default ProfileMetadataProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default SearchesProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default SessionsProvider resource://gre/modules/HealthReport.jsm
-category healthreport-js-provider-default SysInfoProvider resource://gre/modules/HealthReport.jsm
-
-# No Aurora or Beta providers yet; use the categories
-# "healthreport-js-provider-aurora", "healthreport-js-provider-beta".
deleted file mode 100644
--- a/services/healthreport/healthreporter.jsm
+++ /dev/null
@@ -1,1505 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MERGED_COMPARTMENT
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["HealthReporter"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://services-common/async.js");
-
-Cu.import("resource://services-common/bagheeraclient.js");
-#endif
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/ClientID.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
-                                  "resource://gre/modules/TelemetryController.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
-                                  "resource://gre/modules/UpdateUtils.jsm");
-
-// Oldest year to allow in date preferences. This module was implemented in
-// 2012 and no dates older than that should be encountered.
-const OLDEST_ALLOWED_YEAR = 2012;
-
-const DAYS_IN_PAYLOAD = 180;
-
-const DEFAULT_DATABASE_NAME = "healthreport.sqlite";
-
-const TELEMETRY_INIT = "HEALTHREPORT_INIT_MS";
-const TELEMETRY_INIT_FIRSTRUN = "HEALTHREPORT_INIT_FIRSTRUN_MS";
-const TELEMETRY_DB_OPEN = "HEALTHREPORT_DB_OPEN_MS";
-const TELEMETRY_DB_OPEN_FIRSTRUN = "HEALTHREPORT_DB_OPEN_FIRSTRUN_MS";
-const TELEMETRY_GENERATE_PAYLOAD = "HEALTHREPORT_GENERATE_JSON_PAYLOAD_MS";
-const TELEMETRY_JSON_PAYLOAD_SERIALIZE = "HEALTHREPORT_JSON_PAYLOAD_SERIALIZE_MS";
-const TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED = "HEALTHREPORT_PAYLOAD_UNCOMPRESSED_BYTES";
-const TELEMETRY_PAYLOAD_SIZE_COMPRESSED = "HEALTHREPORT_PAYLOAD_COMPRESSED_BYTES";
-const TELEMETRY_UPLOAD = "HEALTHREPORT_UPLOAD_MS";
-const TELEMETRY_COLLECT_CONSTANT = "HEALTHREPORT_COLLECT_CONSTANT_DATA_MS";
-const TELEMETRY_COLLECT_DAILY = "HEALTHREPORT_COLLECT_DAILY_MS";
-const TELEMETRY_SHUTDOWN = "HEALTHREPORT_SHUTDOWN_MS";
-const TELEMETRY_COLLECT_CHECKPOINT = "HEALTHREPORT_POST_COLLECT_CHECKPOINT_MS";
-
-
-/**
- * Helper type to assist with management of Health Reporter state.
- *
- * Instances are not meant to be created outside of a HealthReporter instance.
- *
- * There are two types of IDs associated with clients.
- *
- * Since the beginning of FHR, there has existed a per-upload ID: a UUID is
- * generated at upload time and associated with the state before upload starts.
- * That same upload includes a request to delete all other upload IDs known by
- * the client.
- *
- * Per-upload IDs had the unintended side-effect of creating "orphaned"
- * records/upload IDs on the server. So, a stable client identifer has been
- * introduced. This client identifier is generated when it's missing and sent
- * as part of every upload.
- *
- * There is a high chance we may remove upload IDs in the future.
- */
-function HealthReporterState(reporter) {
-  this._reporter = reporter;
-
-  let profD = OS.Constants.Path.profileDir;
-
-  if (!profD || !profD.length) {
-    throw new Error("Could not obtain profile directory. OS.File not " +
-                    "initialized properly?");
-  }
-
-  this._log = reporter._log;
-
-  this._stateDir = OS.Path.join(profD, "healthreport");
-
-  // To facilitate testing.
-  let leaf = reporter._stateLeaf || "state.json";
-
-  this._filename = OS.Path.join(this._stateDir, leaf);
-  this._log.debug("Storing state in " + this._filename);
-  this._s = null;
-}
-
-HealthReporterState.prototype = Object.freeze({
-  /**
-   * Persistent string identifier associated with this client.
-   */
-  get clientID() {
-    return this._s.clientID;
-  },
-
-  /**
-   * The version associated with the client ID.
-   */
-  get clientIDVersion() {
-    return this._s.clientIDVersion;
-  },
-
-  get lastPingDate() {
-    return new Date(this._s.lastPingTime);
-  },
-
-  get lastSubmitID() {
-    return this._s.remoteIDs[0];
-  },
-
-  get remoteIDs() {
-    return this._s.remoteIDs;
-  },
-
-  get _lastPayloadPath() {
-    return OS.Path.join(this._stateDir, "lastpayload.json");
-  },
-
-  init: function () {
-    return Task.spawn(function* init() {
-      yield OS.File.makeDir(this._stateDir);
-
-      let drsClientID = yield ClientID.getClientID();
-
-      let resetObjectState = function () {
-        this._s = {
-          // The payload version. This is bumped whenever there is a
-          // backwards-incompatible change.
-          v: 1,
-          // The persistent client identifier.
-          clientID: drsClientID,
-          // Denotes the mechanism used to generate the client identifier.
-          // 1: Random UUID.
-          clientIDVersion: 1,
-          // Upload IDs that might be on the server.
-          remoteIDs: [],
-          // When we last performed an uploaded.
-          lastPingTime: 0,
-          // Tracks whether we removed an outdated payload.
-          removedOutdatedLastpayload: false,
-        };
-      }.bind(this);
-
-      try {
-        this._s = yield CommonUtils.readJSON(this._filename);
-      } catch (ex if ex instanceof OS.File.Error &&
-               ex.becauseNoSuchFile) {
-        this._log.warn("Saved state file does not exist.");
-        resetObjectState();
-      } catch (ex) {
-        this._log.error("Exception when reading state from disk", ex);
-        resetObjectState();
-
-        // Don't save in case it goes away on next run.
-      }
-
-      if (typeof(this._s) != "object") {
-        this._log.warn("Read state is not an object. Resetting state.");
-        resetObjectState();
-        yield this.save();
-      }
-
-      if (this._s.v != 1) {
-        this._log.warn("Unknown version in state file: " + this._s.v);
-        resetObjectState();
-        // We explicitly don't save here in the hopes an application re-upgrade
-        // comes along and fixes us.
-      }
-
-      this._s.clientID = drsClientID;
-
-      // Always look for preferences. This ensures that downgrades followed
-      // by reupgrades don't result in excessive data loss.
-      for (let promise of this._migratePrefs()) {
-        yield promise;
-      }
-    }.bind(this));
-  },
-
-  save: function () {
-    this._log.info("Writing state file: " + this._filename);
-    return CommonUtils.writeJSON(this._s, this._filename);
-  },
-
-  addRemoteID: function (id) {
-    this._log.warn("Recording new remote ID: " + id);
-    this._s.remoteIDs.push(id);
-    return this.save();
-  },
-
-  removeRemoteID: function (id) {
-    return this.removeRemoteIDs(id ? [id] : []);
-  },
-
-  removeRemoteIDs: function (ids) {
-    if (!ids || !ids.length) {
-      this._log.warn("No IDs passed for removal.");
-      return Promise.resolve();
-    }
-
-    this._log.warn("Removing documents from remote ID list: " + ids);
-    let filtered = this._s.remoteIDs.filter((x) => ids.indexOf(x) === -1);
-
-    if (filtered.length == this._s.remoteIDs.length) {
-      return Promise.resolve();
-    }
-
-    this._s.remoteIDs = filtered;
-    return this.save();
-  },
-
-  setLastPingDate: function (date) {
-    this._s.lastPingTime = date.getTime();
-
-    return this.save();
-  },
-
-  updateLastPingAndRemoveRemoteID: function (date, id) {
-    return this.updateLastPingAndRemoveRemoteIDs(date, id ? [id] : []);
-  },
-
-  updateLastPingAndRemoveRemoteIDs: function (date, ids) {
-    if (!ids) {
-      return this.setLastPingDate(date);
-    }
-
-    this._log.info("Recording last ping time and deleted remote document.");
-    this._s.lastPingTime = date.getTime();
-    return this.removeRemoteIDs(ids);
-  },
-
-  _migratePrefs: function () {
-    let prefs = this._reporter._prefs;
-
-    let lastID = prefs.get("lastSubmitID", null);
-    let lastPingDate = CommonUtils.getDatePref(prefs, "lastPingTime",
-                                               0, this._log, OLDEST_ALLOWED_YEAR);
-
-    // If we have state from prefs, migrate and save it to a file then clear
-    // out old prefs.
-    if (lastID || (lastPingDate && lastPingDate.getTime() > 0)) {
-      this._log.warn("Migrating saved state from preferences.");
-
-      if (lastID) {
-        this._log.info("Migrating last saved ID: " + lastID);
-        this._s.remoteIDs.push(lastID);
-      }
-
-      let ourLast = this.lastPingDate;
-
-      if (lastPingDate && lastPingDate.getTime() > ourLast.getTime()) {
-        this._log.info("Migrating last ping time: " + lastPingDate);
-        this._s.lastPingTime = lastPingDate.getTime();
-      }
-
-      yield this.save();
-      prefs.reset(["lastSubmitID", "lastPingTime"]);
-    } else {
-      this._log.debug("No prefs data found.");
-    }
-  },
-});
-
-/**
- * This is the abstract base class of `HealthReporter`. It exists so that
- * we can sanely divide work on platforms where control of Firefox Health
- * Report is outside of Gecko (e.g., Android).
- */
-function AbstractHealthReporter(branch, policy, sessionRecorder) {
-  if (!branch.endsWith(".")) {
-    throw new Error("Branch must end with a period (.): " + branch);
-  }
-
-  if (!policy) {
-    throw new Error("Must provide policy to HealthReporter constructor.");
-  }
-
-  this._log = Log.repository.getLogger("Services.HealthReport.HealthReporter");
-  this._log.info("Initializing health reporter instance against " + branch);
-
-  this._branch = branch;
-  this._prefs = new Preferences(branch);
-
-  this._policy = policy;
-  this.sessionRecorder = sessionRecorder;
-
-  this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME;
-
-  this._storage = null;
-  this._storageInProgress = false;
-  this._providerManager = null;
-  this._providerManagerInProgress = false;
-  this._initializeStarted = false;
-  this._initialized = false;
-  this._initializeHadError = false;
-  this._initializedDeferred = Promise.defer();
-  this._shutdownRequested = false;
-  this._shutdownInitiated = false;
-  this._shutdownComplete = false;
-  this._deferredShutdown = Promise.defer();
-  this._promiseShutdown = this._deferredShutdown.promise;
-
-  this._errors = [];
-
-  this._lastDailyDate = null;
-
-  // Yes, this will probably run concurrently with remaining constructor work.
-  let hasFirstRun = this._prefs.get("service.firstRun", false);
-  this._initHistogram = hasFirstRun ? TELEMETRY_INIT : TELEMETRY_INIT_FIRSTRUN;
-  this._dbOpenHistogram = hasFirstRun ? TELEMETRY_DB_OPEN : TELEMETRY_DB_OPEN_FIRSTRUN;
-
-  // This is set to the name for the provider that we are currently initializing,
-  // shutting down or collecting data from, if any.
-  // This is used for AsyncShutdownTimeout diagnostics.
-  this._currentProviderInShutdown = null;
-  this._currentProviderInInit = null;
-  this._currentProviderInCollect = null;
-}
-
-AbstractHealthReporter.prototype = Object.freeze({
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * Whether the service is fully initialized and running.
-   *
-   * If this is false, it is not safe to call most functions.
-   */
-  get initialized() {
-    return this._initialized;
-  },
-
-  /**
-   * Initialize the instance.
-   *
-   * This must be called once after object construction or the instance is
-   * useless.
-   */
-  init: function () {
-    if (this._initializeStarted) {
-      throw new Error("We have already started initialization.");
-    }
-
-    this._initializeStarted = true;
-
-    return Task.spawn(function*() {
-      TelemetryStopwatch.start(this._initHistogram, this);
-
-      try {
-        yield this._state.init();
-
-        if (!this._state._s.removedOutdatedLastpayload) {
-          yield this._deleteOldLastPayload();
-          this._state._s.removedOutdatedLastpayload = true;
-          // Normally we should save this to a file but it directly conflicts with
-          // the "application re-upgrade" decision in HealthReporterState::init()
-          // which specifically does not save the state to a file.
-        }
-      } catch (ex) {
-        this._log.error("Error deleting last payload", ex);
-      }
-
-      // As soon as we have could have storage, we need to register cleanup or
-      // else bad things happen on shutdown.
-      Services.obs.addObserver(this, "quit-application", false);
-
-      // The database needs to be shut down by the end of shutdown
-      // phase profileBeforeChange.
-      Metrics.Storage.shutdown.addBlocker("FHR: Flushing storage shutdown",
-        () => {
-          // Workaround bug 1017706
-          // Apparently, in some cases, quit-application is not triggered
-          // (or is triggered after profile-before-change), so we need to
-          // make sure that `_initiateShutdown()` is triggered at least
-          // once.
-          this._initiateShutdown();
-          return this._promiseShutdown;
-        },
-        () => ({
-            shutdownInitiated: this._shutdownInitiated,
-            initialized: this._initialized,
-            shutdownRequested: this._shutdownRequested,
-            initializeHadError: this._initializeHadError,
-            providerManagerInProgress: this._providerManagerInProgress,
-            storageInProgress: this._storageInProgress,
-            hasProviderManager: !!this._providerManager,
-            hasStorage: !!this._storage,
-            shutdownComplete: this._shutdownComplete,
-            currentProviderInShutdown: this._currentProviderInShutdown,
-            currentProviderInInit: this._currentProviderInInit,
-            currentProviderInCollect: this._currentProviderInCollect,
-          }));
-
-      try {
-        this._storageInProgress = true;
-        TelemetryStopwatch.start(this._dbOpenHistogram, this);
-        let storage = yield Metrics.Storage(this._dbName);
-        TelemetryStopwatch.finish(this._dbOpenHistogram, this);
-        yield this._onStorageCreated();
-
-        delete this._dbOpenHistogram;
-        this._log.info("Storage initialized.");
-        this._storage = storage;
-        this._storageInProgress = false;
-
-        if (this._shutdownRequested) {
-          this._initiateShutdown();
-          return null;
-        }
-
-        yield this._initializeProviderManager();
-        yield this._onProviderManagerInitialized();
-        this._initializedDeferred.resolve();
-        return this.onInit();
-      } catch (ex) {
-        yield this._onInitError(ex);
-        this._initializedDeferred.reject(ex);
-      }
-    }.bind(this));
-  },
-
-  //----------------------------------------------------
-  // SERVICE CONTROL FUNCTIONS
-  //
-  // You shouldn't need to call any of these externally.
-  //----------------------------------------------------
-
-  _onInitError: function (error) {
-    TelemetryStopwatch.cancel(this._initHistogram, this);
-    TelemetryStopwatch.cancel(this._dbOpenHistogram, this);
-    delete this._initHistogram;
-    delete this._dbOpenHistogram;
-
-    this._recordError("Error during initialization", error);
-    this._initializeHadError = true;
-    this._initiateShutdown();
-    return Promise.reject(error);
-
-    // FUTURE consider poisoning prototype's functions so calls fail with a
-    // useful error message.
-  },
-
-
-  /**
-   * Removes the outdated lastpaylaod.json and lastpayload.json.tmp files
-   * @see Bug #867902
-   * @return a promise for when all the files have been deleted
-   */
-  _deleteOldLastPayload: function () {
-    let paths = [this._state._lastPayloadPath, this._state._lastPayloadPath + ".tmp"];
-    return Task.spawn(function removeAllFiles () {
-      for (let path of paths) {
-        try {
-          OS.File.remove(path);
-        } catch (ex) {
-          if (!ex.becauseNoSuchFile) {
-            this._log.error("Exception when removing outdated payload files", ex);
-          }
-        }
-      }
-    }.bind(this));
-  },
-
-  _initializeProviderManager: Task.async(function* _initializeProviderManager() {
-    if (this._collector) {
-      throw new Error("Provider manager has already been initialized.");
-    }
-
-    this._log.info("Initializing provider manager.");
-    this._providerManager = new Metrics.ProviderManager(this._storage);
-    this._providerManager.onProviderError = this._recordError.bind(this);
-    this._providerManager.onProviderInit = this._initProvider.bind(this);
-    this._providerManagerInProgress = true;
-
-    let catString = this._prefs.get("service.providerCategories") || "";
-    if (catString.length) {
-      for (let category of catString.split(",")) {
-        yield this._providerManager.registerProvidersFromCategoryManager(category,
-                     providerName => this._currentProviderInInit = providerName);
-      }
-      this._currentProviderInInit = null;
-    }
-  }),
-
-  _onProviderManagerInitialized: function () {
-    TelemetryStopwatch.finish(this._initHistogram, this);
-    delete this._initHistogram;
-    this._log.debug("Provider manager initialized.");
-    this._providerManagerInProgress = false;
-
-    if (this._shutdownRequested) {
-      this._initiateShutdown();
-      return;
-    }
-
-    this._log.info("HealthReporter started.");
-    this._initialized = true;
-    Services.obs.addObserver(this, "idle-daily", false);
-
-    // If upload is not enabled, ensure daily collection works. If upload
-    // is enabled, this will be performed as part of upload.
-    //
-    // This is important because it ensures about:healthreport contains
-    // longitudinal data even if upload is disabled. Having about:healthreport
-    // provide useful info even if upload is disabled was a core launch
-    // requirement.
-    //
-    // We do not catch changes to the backing pref. So, if the session lasts
-    // many days, we may fail to collect. However, most sessions are short and
-    // this code will likely be refactored as part of splitting up policy to
-    // serve Android. So, meh.
-    if (!this._policy.healthReportUploadEnabled) {
-      this._log.info("Upload not enabled. Scheduling daily collection.");
-      // Since the timer manager is a singleton and there could be multiple
-      // HealthReporter instances, we need to encode a unique identifier in
-      // the timer ID.
-      try {
-        let timerName = this._branch.replace(/\./g, "-") + "lastDailyCollection";
-        let tm = Cc["@mozilla.org/updates/timer-manager;1"]
-                   .getService(Ci.nsIUpdateTimerManager);
-        tm.registerTimer(timerName, this.collectMeasurements.bind(this),
-                         24 * 60 * 60);
-      } catch (ex) {
-        this._log.error("Error registering collection timer", ex);
-      }
-    }
-
-    // Clean up caches and reduce memory usage.
-    this._storage.compact();
-  },
-
-  // nsIObserver to handle shutdown.
-  observe: function (subject, topic, data) {
-    switch (topic) {
-      case "quit-application":
-        Services.obs.removeObserver(this, "quit-application");
-        this._initiateShutdown();
-        break;
-
-      case "idle-daily":
-        this._performDailyMaintenance();
-        break;
-    }
-  },
-
-  _initiateShutdown: function () {
-    // Ensure we only begin the main shutdown sequence once.
-    if (this._shutdownInitiated) {
-      this._log.warn("Shutdown has already been initiated. No-op.");
-      return;
-    }
-
-    this._log.info("Request to shut down.");
-
-    this._initialized = false;
-    this._shutdownRequested = true;
-
-    if (this._initializeHadError) {
-      this._log.warn("Initialization had error. Shutting down immediately.");
-    } else {
-      if (this._providerManagerInProgress) {
-        this._log.warn("Provider manager is in progress of initializing. " +
-                       "Waiting to finish.");
-        return;
-      }
-
-      // If storage is in the process of initializing, we need to wait for it
-      // to finish before continuing. The initialization process will call us
-      // again once storage has initialized.
-      if (this._storageInProgress) {
-        this._log.warn("Storage is in progress of initializing. Waiting to finish.");
-        return;
-      }
-    }
-
-    this._log.warn("Initiating main shutdown procedure.");
-
-    // Everything from here must only be performed once or else race conditions
-    // could occur.
-
-    TelemetryStopwatch.start(TELEMETRY_SHUTDOWN, this);
-    this._shutdownInitiated = true;
-
-    // We may not have registered the observer yet. If not, this will
-    // throw.
-    try {
-      Services.obs.removeObserver(this, "idle-daily");
-    } catch (ex) { }
-
-    Task.spawn(function*() {
-      try {
-        if (this._providerManager) {
-          this._log.info("Shutting down provider manager.");
-          for (let provider of this._providerManager.providers) {
-            try {
-              this._log.info("Shutting down provider: " + provider.name);
-              this._currentProviderInShutdown = provider.name;
-              yield provider.shutdown();
-            } catch (ex) {
-              this._log.warn("Error when shutting down provider", ex);
-            }
-          }
-          this._log.info("Provider manager shut down.");
-          this._providerManager = null;
-          this._currentProviderInShutdown = null;
-          this._onProviderManagerShutdown();
-        }
-        if (this._storage) {
-          this._log.info("Shutting down storage.");
-          try {
-            yield this._storage.close();
-            yield this._onStorageClose();
-          } catch (error) {
-            this._log.warn("Error when closing storage", error);
-          }
-          this._storage = null;
-        }
-
-        this._log.warn("Shutdown complete.");
-        this._shutdownComplete = true;
-      } finally {
-        this._deferredShutdown.resolve();
-        TelemetryStopwatch.finish(TELEMETRY_SHUTDOWN, this);
-      }
-    }.bind(this));
-  },
-
-  onInit: function() {
-    return this._initializedDeferred.promise;
-  },
-
-  _onStorageCreated: function() {
-    // Do nothing.
-    // This method provides a hook point for the test suite.
-  },
-
-  _onStorageClose: function() {
-    // Do nothing.
-    // This method provides a hook point for the test suite.
-  },
-
-  _onProviderManagerShutdown: function() {
-    // Do nothing.
-    // This method provides a hook point for the test suite.
-  },
-
-  /**
-   * Convenience method to shut down the instance.
-   *
-   * This should *not* be called outside of tests.
-   */
-  _shutdown: function () {
-    this._initiateShutdown();
-    return this._promiseShutdown;
-  },
-
-  _performDailyMaintenance: function () {
-    this._log.info("Request to perform daily maintenance.");
-
-    if (!this._initialized) {
-      return;
-    }
-
-    let now = new Date();
-    let cutoff = new Date(now.getTime() - MILLISECONDS_PER_DAY * (DAYS_IN_PAYLOAD - 1));
-
-    // The operation is enqueued and put in a transaction by the storage module.
-    this._storage.pruneDataBefore(cutoff);
-  },
-
-  //--------------------
-  // Provider Management
-  //--------------------
-
-  /**
-   * Obtain a provider from its name.
-   *
-   * This will only return providers that are currently initialized. If
-   * a provider is lazy initialized (like pull-only providers) this
-   * will likely not return anything.
-   */
-  getProvider: function (name) {
-    if (!this._providerManager) {
-      return null;
-    }
-
-    return this._providerManager.getProvider(name);
-  },
-
-  _initProvider: function (provider) {
-    provider.healthReporter = this;
-  },
-
-  /**
-   * Record an exception for reporting in the payload.
-   *
-   * A side effect is the exception is logged.
-   *
-   * Note that callers need to be extra sensitive about ensuring personal
-   * or otherwise private details do not leak into this. All of the user data
-   * on the stack in FHR code should be limited to data we were collecting with
-   * the intent to submit. So, it is covered under the user's consent to use
-   * the feature.
-   *
-   * @param message
-   *        (string) Human readable message describing error.
-   * @param ex
-   *        (Error) The error that should be captured.
-   */
-  _recordError: function (message, ex) {
-    let recordMessage = message;
-    let logMessage = message;
-
-    if (ex) {
-      recordMessage += ": " + Log.exceptionStr(ex);
-      logMessage += ": " + Log.exceptionStr(ex);
-    }
-
-    // Scrub out potentially identifying information from strings that could
-    // make the payload.
-    let appData = Services.dirsvc.get("UAppData", Ci.nsIFile);
-    let profile = Services.dirsvc.get("ProfD", Ci.nsIFile);
-
-    let appDataURI = Services.io.newFileURI(appData);
-    let profileURI = Services.io.newFileURI(profile);
-
-    // Order of operation is important here. We do the URI before the path version
-    // because the path may be a subset of the URI. We also have to check for the case
-    // where UAppData is underneath the profile directory (or vice-versa) so we
-    // don't substitute incomplete strings.
-
-    // Return a /g regex that matches the provided string exactly.
-    function regexify(s) {
-      return new RegExp(s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"), "g");
-    }
-
-    function replace(uri, path, thing) {
-      // Try is because .spec can throw on invalid URI.
-      try {
-        recordMessage = recordMessage.replace(regexify(uri.spec), "<" + thing + "URI>");
-      } catch (ex) { }
-
-      recordMessage = recordMessage.replace(regexify(path), "<" + thing + "Path>");
-    }
-
-    if (appData.path.includes(profile.path)) {
-      replace(appDataURI, appData.path, 'AppData');
-      replace(profileURI, profile.path, 'Profile');
-    } else {
-      replace(profileURI, profile.path, 'Profile');
-      replace(appDataURI, appData.path, 'AppData');
-    }
-
-    this._log.warn(logMessage);
-    this._errors.push(recordMessage);
-  },
-
-  /**
-   * Collect all measurements for all registered providers.
-   */
-  collectMeasurements: function () {
-    if (!this._initialized) {
-      return Promise.reject(new Error("Not initialized."));
-    }
-
-    return Task.spawn(function doCollection() {
-      yield this._providerManager.ensurePullOnlyProvidersRegistered();
-
-      try {
-        TelemetryStopwatch.start(TELEMETRY_COLLECT_CONSTANT, this);
-        yield this._providerManager.collectConstantData(name => this._currentProviderInCollect = name);
-        this._currentProviderInCollect = null;
-        TelemetryStopwatch.finish(TELEMETRY_COLLECT_CONSTANT, this);
-      } catch (ex) {
-        TelemetryStopwatch.cancel(TELEMETRY_COLLECT_CONSTANT, this);
-        this._log.warn("Error collecting constant data", ex);
-      }
-
-      // Daily data is collected if it hasn't yet been collected this
-      // application session or if it has been more than a day since the
-      // last collection. This means that providers could see many calls to
-      // collectDailyData per calendar day. However, this collection API
-      // makes no guarantees about limits. The alternative would involve
-      // recording state. The simpler implementation prevails for now.
-      if (!this._lastDailyDate ||
-          Date.now() - this._lastDailyDate > MILLISECONDS_PER_DAY) {
-
-        try {
-          TelemetryStopwatch.start(TELEMETRY_COLLECT_DAILY, this);
-          this._lastDailyDate = new Date();
-          yield this._providerManager.collectDailyData(name => this._currentProviderInCollect = name);
-          this._currentProviderInCollect = null;
-          TelemetryStopwatch.finish(TELEMETRY_COLLECT_DAILY, this);
-        } catch (ex) {
-          TelemetryStopwatch.cancel(TELEMETRY_COLLECT_DAILY, this);
-          this._log.warn("Error collecting daily data from providers", ex);
-        }
-      }
-
-      yield this._providerManager.ensurePullOnlyProvidersUnregistered();
-
-      // Flush gathered data to disk. This will incur an fsync. But, if
-      // there is ever a time we want to persist data to disk, it's
-      // after a massive collection.
-      try {
-        TelemetryStopwatch.start(TELEMETRY_COLLECT_CHECKPOINT, this);
-        yield this._storage.checkpoint();
-        TelemetryStopwatch.finish(TELEMETRY_COLLECT_CHECKPOINT, this);
-      } catch (ex) {
-        TelemetryStopwatch.cancel(TELEMETRY_COLLECT_CHECKPOINT, this);
-        throw ex;
-      }
-
-      throw new Task.Result();
-    }.bind(this));
-  },
-
-  /**
-   * Helper function to perform data collection and obtain the JSON payload.
-   *
-   * If you are looking for an up-to-date snapshot of FHR data that pulls in
-   * new data since the last upload, this is how you should obtain it.
-   *
-   * @param asObject
-   *        (bool) Whether to resolve an object or JSON-encoded string of that
-   *        object (the default).
-   *
-   * @return Promise<Object | string>
-   */
-  collectAndObtainJSONPayload: function (asObject=false) {
-    if (!this._initialized) {
-      return Promise.reject(new Error("Not initialized."));
-    }
-
-    return Task.spawn(function collectAndObtain() {
-      yield this._storage.setAutoCheckpoint(0);
-      yield this._providerManager.ensurePullOnlyProvidersRegistered();
-
-      let payload;
-      let error;
-
-      try {
-        yield this.collectMeasurements();
-        payload = yield this.getJSONPayload(asObject);
-      } catch (ex) {
-        error = ex;
-        this._collectException("Error collecting and/or retrieving JSON payload",
-                               ex);
-      } finally {
-        yield this._providerManager.ensurePullOnlyProvidersUnregistered();
-        yield this._storage.setAutoCheckpoint(1);
-
-        if (error) {
-          throw error;
-        }
-      }
-
-      // We hold off throwing to ensure that behavior between finally
-      // and generators and throwing is sane.
-      throw new Task.Result(payload);
-    }.bind(this));
-  },
-
-
-  /**
-   * Obtain the JSON payload for currently-collected data.
-   *
-   * The payload only contains data that has been recorded to FHR. Some
-   * providers may have newer data available. If you want to ensure you
-   * have all available data, call `collectAndObtainJSONPayload`
-   * instead.
-   *
-   * @param asObject
-   *        (bool) Whether to return an object or JSON encoding of that
-   *        object (the default).
-   *
-   * @return Promise<string|object>
-   */
-  getJSONPayload: function (asObject=false) {
-    TelemetryStopwatch.start(TELEMETRY_GENERATE_PAYLOAD, this);
-    let deferred = Promise.defer();
-
-    Task.spawn(this._getJSONPayload.bind(this, this._now(), asObject)).then(
-      function onResult(result) {
-        TelemetryStopwatch.finish(TELEMETRY_GENERATE_PAYLOAD, this);
-        deferred.resolve(result);
-      }.bind(this),
-      function onError(error) {
-        TelemetryStopwatch.cancel(TELEMETRY_GENERATE_PAYLOAD, this);
-        deferred.reject(error);
-      }.bind(this)
-    );
-
-    return deferred.promise;
-  },
-
-  _getJSONPayload: function (now, asObject=false) {
-    let pingDateString = this._formatDate(now);
-    this._log.info("Producing JSON payload for " + pingDateString);
-
-    // May not be present if we are generating as a result of init error.
-    if (this._providerManager) {
-      yield this._providerManager.ensurePullOnlyProvidersRegistered();
-    }
-
-    let o = {
-      version: 2,
-      clientID: this._state.clientID,
-      clientIDVersion: this._state.clientIDVersion,
-      thisPingDate: pingDateString,
-      geckoAppInfo: this.obtainAppInfo(this._log),
-      data: {last: {}, days: {}},
-    };
-
-    let outputDataDays = o.data.days;
-
-    // Guard here in case we don't track this (e.g., on Android).
-    let lastPingDate = this.lastPingDate;
-    if (lastPingDate && lastPingDate.getTime() > 0) {
-      o.lastPingDate = this._formatDate(lastPingDate);
-    }
-
-    // We can still generate a payload even if we're not initialized.
-    // This is to facilitate error upload on init failure.
-    if (this._initialized) {
-      for (let provider of this._providerManager.providers) {
-        let providerName = provider.name;
-
-        let providerEntry = {
-          measurements: {},
-        };
-
-        // Measurement name to recorded version.
-        let lastVersions = {};
-        // Day string to mapping of measurement name to recorded version.
-        let dayVersions = {};
-
-        for (let [measurementKey, measurement] of provider.measurements) {
-          let name = providerName + "." + measurement.name;
-          let version = measurement.version;
-
-          let serializer;
-          try {
-            // The measurement is responsible for returning a serializer which
-            // is aware of the measurement version.
-            serializer = measurement.serializer(measurement.SERIALIZE_JSON);
-          } catch (ex) {
-            this._recordError("Error obtaining serializer for measurement: " +
-                              name, ex);
-            continue;
-          }
-
-          let data;
-          try {
-            data = yield measurement.getValues();
-          } catch (ex) {
-            this._recordError("Error obtaining data for measurement: " + name,
-                              ex);
-            continue;
-          }
-
-          if (data.singular.size) {
-            try {
-              let serialized = serializer.singular(data.singular);
-              if (serialized) {
-                // Only replace the existing data if there is no data or if our
-                // version is newer than the old one.
-                if (!(name in o.data.last) || version > lastVersions[name]) {
-                  o.data.last[name] = serialized;
-                  lastVersions[name] = version;
-                }
-              }
-            } catch (ex) {
-              this._recordError("Error serializing singular data: " + name,
-                                ex);
-              continue;
-            }
-          }
-
-          let dataDays = data.days;
-          for (let i = 0; i < DAYS_IN_PAYLOAD; i++) {
-            let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
-            if (!dataDays.hasDay(date)) {
-              continue;
-            }
-            let dateFormatted = this._formatDate(date);
-
-            try {
-              let serialized = serializer.daily(dataDays.getDay(date));
-              if (!serialized) {
-                continue;
-              }
-
-              if (!(dateFormatted in outputDataDays)) {
-                outputDataDays[dateFormatted] = {};
-              }
-
-              // This needs to be separate because dayVersions is provider
-              // specific and gets blown away in a loop while outputDataDays
-              // is persistent.
-              if (!(dateFormatted in dayVersions)) {
-                dayVersions[dateFormatted] = {};
-              }
-
-              if (!(name in outputDataDays[dateFormatted]) ||
-                  version > dayVersions[dateFormatted][name]) {
-                outputDataDays[dateFormatted][name] = serialized;
-                dayVersions[dateFormatted][name] = version;
-              }
-            } catch (ex) {
-              this._recordError("Error populating data for day: " + name, ex);
-              continue;
-            }
-          }
-        }
-      }
-    } else {
-      o.notInitialized = 1;
-      this._log.warn("Not initialized. Sending report with only error info.");
-    }
-
-    if (this._errors.length) {
-      o.errors = this._errors.slice(0, 20);
-    }
-
-    if (this._initialized) {
-      this._storage.compact();
-    }
-
-    if (!asObject) {
-      TelemetryStopwatch.start(TELEMETRY_JSON_PAYLOAD_SERIALIZE, this);
-      o = JSON.stringify(o);
-      TelemetryStopwatch.finish(TELEMETRY_JSON_PAYLOAD_SERIALIZE, this);
-    }
-
-    if (this._providerManager) {
-      yield this._providerManager.ensurePullOnlyProvidersUnregistered();
-    }
-
-    throw new Task.Result(o);
-  },
-
-  _now: function _now() {
-    return new Date();
-  },
-
-  // These are stolen from AppInfoProvider.
-  appInfoVersion: 1,
-  appInfoFields: {
-    // From nsIXULAppInfo.
-    vendor: "vendor",
-    name: "name",
-    id: "ID",
-    version: "version",
-    appBuildID: "appBuildID",
-    platformVersion: "platformVersion",
-    platformBuildID: "platformBuildID",
-
-    // From nsIXULRuntime.
-    os: "OS",
-    xpcomabi: "XPCOMABI",
-  },
-
-  /**
-   * Statically return a bundle of app info data, a subset of that produced by
-   * AppInfoProvider._populateConstants. This allows us to more usefully handle
-   * payloads that, due to error, contain no data.
-   *
-   * Returns a very sparse object if Services.appinfo is unavailable.
-   */
-  obtainAppInfo: function () {
-    let out = {"_v": this.appInfoVersion};
-    try {
-      let ai = Services.appinfo;
-      for (let [k, v] in Iterator(this.appInfoFields)) {
-        out[k] = ai[v];
-      }
-    } catch (ex) {
-      this._log.warn("Could not obtain Services.appinfo", ex);
-    }
-
-    try {
-      out["updateChannel"] = UpdateUtils.UpdateChannel;
-    } catch (ex) {
-      this._log.warn("Could not obtain update channel", ex);
-    }
-
-    return out;
-  },
-});
-
-/**
- * HealthReporter and its abstract superclass coordinate collection and
- * submission of health report metrics.
- *
- * This is the main type for Firefox Health Report on desktop. It glues all the
- * lower-level components (such as collection and submission) together.
- *
- * An instance of this type is created as an XPCOM service. See
- * DataReportingService.js and
- * DataReporting.manifest/HealthReportComponents.manifest.
- *
- * It is theoretically possible to have multiple instances of this running
- * in the application. For example, this type may one day handle submission
- * of telemetry data as well. However, there is some moderate coupling between
- * this type and *the* Firefox Health Report (e.g., the policy). This could
- * be abstracted if needed.
- *
- * Note that `AbstractHealthReporter` exists to allow for Firefox Health Report
- * to be more easily implemented on platforms where a separate controlling
- * layer is responsible for payload upload and deletion.
- *
- * IMPLEMENTATION NOTES
- * ====================
- *
- * These notes apply to the combination of `HealthReporter` and
- * `AbstractHealthReporter`.
- *
- * Initialization and shutdown are somewhat complicated and worth explaining
- * in extra detail.
- *
- * The complexity is driven by the requirements of SQLite connection management.
- * Once you have a SQLite connection, it isn't enough to just let the
- * application shut down. If there is an open connection or if there are
- * outstanding SQL statements come XPCOM shutdown time, Storage will assert.
- * On debug builds you will crash. On release builds you will get a shutdown
- * hang. This must be avoided!
- *
- * During initialization, the second we create a SQLite connection (via
- * Metrics.Storage) we register observers for application shutdown. The
- * "quit-application" notification initiates our shutdown procedure. The
- * subsequent "profile-do-change" notification ensures it has completed.
- *
- * The handler for "profile-do-change" may result in event loop spinning. This
- * is because of race conditions between our shutdown code and application
- * shutdown.
- *
- * All of our shutdown routines are async. There is the potential that these
- * async functions will not complete before XPCOM shutdown. If they don't
- * finish in time, we could get assertions in Storage. Our solution is to
- * initiate storage early in the shutdown cycle ("quit-application").
- * Hopefully all the async operations have completed by the time we reach
- * "profile-do-change." If so, great. If not, we spin the event loop until
- * they have completed, avoiding potential race conditions.
- *
- * @param branch
- *        (string) The preferences branch to use for state storage. The value
- *        must end with a period (.).
- *
- * @param policy
- *        (HealthReportPolicy) Policy driving execution of HealthReporter.
- */
-this.HealthReporter = function (branch, policy, stateLeaf=null) {
-  this._stateLeaf = stateLeaf;
-  this._uploadInProgress = false;
-
-  AbstractHealthReporter.call(this, branch, policy, TelemetryController.getSessionRecorder());
-
-  if (!this.serverURI) {
-    throw new Error("No server URI defined. Did you forget to define the pref?");
-  }
-
-  if (!this.serverNamespace) {
-    throw new Error("No server namespace defined. Did you forget a pref?");
-  }
-
-  this._state = new HealthReporterState(this);
-}
-
-this.HealthReporter.prototype = Object.freeze({
-  __proto__: AbstractHealthReporter.prototype,
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
-  get lastSubmitID() {
-    return this._state.lastSubmitID;
-  },
-
-  /**
-   * When we last successfully submitted data to the server.
-   *
-   * This is sent as part of the upload. This is redundant with similar data
-   * in the policy because we like the modules to be loosely coupled and the
-   * similar data in the policy is only used for forensic purposes.
-   */
-  get lastPingDate() {
-    return this._state.lastPingDate;
-  },
-
-  /**
-   * The base URI of the document server to which to submit data.
-   *
-   * This is typically a Bagheera server instance. It is the URI up to but not
-   * including the version prefix. e.g. https://data.metrics.mozilla.com/
-   */
-  get serverURI() {
-    return this._prefs.get("documentServerURI", null);
-  },
-
-  set serverURI(value) {
-    if (!value) {
-      throw new Error("serverURI must have a value.");
-    }
-
-    if (typeof(value) != "string") {
-      throw new Error("serverURI must be a string: " + value);
-    }
-
-    this._prefs.set("documentServerURI", value);
-  },
-
-  /**
-   * The namespace on the document server to which we will be submitting data.
-   */
-  get serverNamespace() {
-    return this._prefs.get("documentServerNamespace", "metrics");
-  },
-
-  set serverNamespace(value) {
-    if (!value) {
-      throw new Error("serverNamespace must have a value.");
-    }
-
-    if (typeof(value) != "string") {
-      throw new Error("serverNamespace must be a string: " + value);
-    }
-
-    this._prefs.set("documentServerNamespace", value);
-  },
-
-  /**
-   * Whether this instance will upload data to a server.
-   */
-  get willUploadData() {
-    return  this._policy.userNotifiedOfCurrentPolicy &&
-            this._policy.healthReportUploadEnabled;
-  },
-
-  /**
-   * Whether remote data is currently stored.
-   *
-   * @return bool
-   */
-  haveRemoteData: function () {
-    return !!this._state.lastSubmitID;
-  },
-
-  /**
-   * Called to initiate a data upload.
-   *
-   * The passed argument is a `DataSubmissionRequest` from policy.jsm.
-   */
-  requestDataUpload: function (request) {
-    if (!this._initialized) {
-      return Promise.reject(new Error("Not initialized."));
-    }
-
-    return Task.spawn(function doUpload() {
-      yield this._providerManager.ensurePullOnlyProvidersRegistered();
-      try {
-        yield this.collectMeasurements();
-        try {
-          yield this._uploadData(request);
-        } catch (ex) {
-          this._onSubmitDataRequestFailure(ex);
-        }
-      } finally {
-        yield this._providerManager.ensurePullOnlyProvidersUnregistered();
-      }
-    }.bind(this));
-  },
-
-  /**
-   * Request that server data be deleted.
-   *
-   * If deletion is scheduled to occur immediately, a promise will be returned
-   * that will be fulfilled when the deletion attempt finishes. Otherwise,
-   * callers should poll haveRemoteData() to determine when remote data is
-   * deleted.
-   */
-  requestDeleteRemoteData: function (reason) {
-    if (!this.haveRemoteData()) {
-      return;
-    }
-
-    return this._policy.deleteRemoteData(reason);
-  },
-
-  /**
-   * Override default handler to incur an upload describing the error.
-   */
-  _onInitError: function (error) {
-    // Need to capture this before we call the parent else it's always
-    // set.
-    let inShutdown = this._shutdownRequested;
-    let result;
-
-    try {
-      result = AbstractHealthReporter.prototype._onInitError.call(this, error);
-    } catch (ex) {
-      this._log.error("Error when calling _onInitError", ex);
-    }
-
-    // This bypasses a lot of the checks in policy, such as respect for
-    // backoff. We should arguably not do this. However, reporting
-    // startup errors is important. And, they should not occur with much
-    // frequency in the wild. So, it shouldn't be too big of a deal.
-    if (!inShutdown &&
-        this._policy.healthReportUploadEnabled &&
-        this._policy.ensureUserNotified()) {
-      // We don't care about what happens to this request. It's best
-      // effort.
-      let request = {
-        onNoDataAvailable: function () {},
-        onSubmissionSuccess: function () {},
-        onSubmissionFailureSoft: function () {},
-        onSubmissionFailureHard: function () {},
-        onUploadInProgress: function () {},
-      };
-
-      this._uploadData(request);
-    }
-
-    return result;
-  },
-
-  _onBagheeraResult: function (request, isDelete, date, result) {
-    this._log.debug("Received Bagheera result.");
-
-    return Task.spawn(function onBagheeraResult() {
-      let hrProvider = this.getProvider("org.mozilla.healthreport");
-
-      if (!result.transportSuccess) {
-        // The built-in provider may not be initialized if this instance failed
-        // to initialize fully.
-        if (hrProvider && !isDelete) {
-          try {
-            hrProvider.recordEvent("uploadTransportFailure", date);
-          } catch (ex) {
-            this._log.error("Error recording upload transport failure", ex);
-          }
-        }
-
-        request.onSubmissionFailureSoft("Network transport error.");
-        throw new Task.Result(false);
-      }
-
-      if (!result.serverSuccess) {
-        if (hrProvider && !isDelete) {
-          try {
-            hrProvider.recordEvent("uploadServerFailure", date);
-          } catch (ex) {
-            this._log.error("Error recording server failure", ex);
-          }
-        }
-
-        request.onSubmissionFailureHard("Server failure.");
-        throw new Task.Result(false);
-      }
-
-      if (hrProvider && !isDelete) {
-        try {
-          hrProvider.recordEvent("uploadSuccess", date);
-        } catch (ex) {
-          this._log.error("Error recording upload success", ex);
-        }
-      }
-
-      if (isDelete) {
-        this._log.warn("Marking delete as successful.");
-        yield this._state.removeRemoteIDs([result.id]);
-      } else {
-        this._log.warn("Marking upload as successful.");
-        yield this._state.updateLastPingAndRemoveRemoteIDs(date, result.deleteIDs);
-      }
-
-      request.onSubmissionSuccess(this._now());
-
-      throw new Task.Result(true);
-    }.bind(this));
-  },
-
-  _onSubmitDataRequestFailure: function (error) {
-    this._log.error("Error processing request to submit data", error);
-  },
-
-  _formatDate: function (date) {
-    // Why, oh, why doesn't JS have a strftime() equivalent?
-    return date.toISOString().substr(0, 10);
-  },
-
-  _uploadData: function (request) {
-    // Under ideal circumstances, clients should never race to this
-    // function. However, server logs have observed behavior where
-    // racing to this function could be a cause. So, this lock was
-    // instituted.
-    if (this._uploadInProgress) {
-      this._log.warn("Upload requested but upload already in progress.");
-      let provider = this.getProvider("org.mozilla.healthreport");
-      let promise = provider.recordEvent("uploadAlreadyInProgress");
-      request.onUploadInProgress("Upload already in progress.");
-      return promise;
-    }
-
-    let id = CommonUtils.generateUUID();
-
-    this._log.info("Uploading data to server: " + this.serverURI + " " +
-                   this.serverNamespace + ":" + id);
-    let client = new BagheeraClient(this.serverURI);
-    let now = this._now();
-
-    return Task.spawn(function doUpload() {
-      try {
-        // The test for upload locking monkeypatches getJSONPayload.
-        // If the next two lines change, be sure to verify the test is
-        // accurate!
-        this._uploadInProgress = true;
-        let payload = yield this.getJSONPayload();
-
-        let histogram = Services.telemetry.getHistogramById(TELEMETRY_PAYLOAD_SIZE_UNCOMPRESSED);
-        histogram.add(payload.length);
-
-        let lastID = this.lastSubmitID;
-        yield this._state.addRemoteID(id);
-
-        let hrProvider = this.getProvider("org.mozilla.healthreport");
-        if (hrProvider) {
-          let event = lastID ? "continuationUploadAttempt"
-                             : "firstDocumentUploadAttempt";
-          try {
-            hrProvider.recordEvent(event, now);
-          } catch (ex) {
-            this._log.error("Error when recording upload attempt", ex);
-          }
-        }
-
-        TelemetryStopwatch.start(TELEMETRY_UPLOAD, this);
-        let result;
-        try {
-          let options = {
-            deleteIDs: this._state.remoteIDs.filter((x) => { return x != id; }),
-            telemetryCompressed: TELEMETRY_PAYLOAD_SIZE_COMPRESSED,
-          };
-          result = yield client.uploadJSON(this.serverNamespace, id, payload,
-                                           options);
-          TelemetryStopwatch.finish(TELEMETRY_UPLOAD, this);
-        } catch (ex) {
-          TelemetryStopwatch.cancel(TELEMETRY_UPLOAD, this);
-          if (hrProvider) {
-            try {
-              hrProvider.recordEvent("uploadClientFailure", now);
-            } catch (ex) {
-              this._log.error("Error when recording client failure", ex);
-            }
-          }
-          throw ex;
-        }
-
-        yield this._onBagheeraResult(request, false, now, result);
-      } finally {
-        this._uploadInProgress = false;
-      }
-    }.bind(this));
-  },
-
-  /**
-   * Request deletion of remote data.
-   *
-   * @param request
-   *        (DataSubmissionRequest) Tracks progress of this request.
-   */
-  deleteRemoteData: function (request) {
-    if (!this._state.lastSubmitID) {
-      this._log.info("Received request to delete remote data but no data stored.");
-      request.onNoDataAvailable();
-      return;
-    }
-
-    this._log.warn("Deleting remote data.");
-    let client = new BagheeraClient(this.serverURI);
-
-    return Task.spawn(function* doDelete() {
-      try {
-        let result = yield client.deleteDocument(this.serverNamespace,
-                                                 this.lastSubmitID);
-        yield this._onBagheeraResult(request, true, this._now(), result);
-      } catch (ex) {
-        this._log.error("Error processing request to delete data", ex);
-      }
-    }.bind(this));
-  },
-});
-
deleted file mode 100644
--- a/services/healthreport/modules-testing/utils.jsm
+++ /dev/null
@@ -1,198 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "getAppInfo",
-  "updateAppInfo",
-  "createFakeCrash",
-  "InspectedHealthReporter",
-  "getHealthReporter",
-];
-
-
-const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/services-common/utils.js");
-Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-
-var APP_INFO = {
-  vendor: "Mozilla",
-  name: "xpcshell",
-  ID: "xpcshell@tests.mozilla.org",
-  version: "1",
-  appBuildID: "20121107",
-  platformVersion: "p-ver",
-  platformBuildID: "20121106",
-  inSafeMode: false,
-  logConsoleErrors: true,
-  OS: "XPCShell",
-  XPCOMABI: "noarch-spidermonkey",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
-  invalidateCachesOnRestart: function() {},
-};
-
-
-/**
- * Obtain a reference to the current object used to define XULAppInfo.
- */
-this.getAppInfo = function () { return APP_INFO; }
-
-/**
- * Update the current application info.
- *
- * If the argument is defined, it will be the object used. Else, APP_INFO is
- * used.
- *
- * To change the current XULAppInfo, simply call this function. If there was
- * a previously registered app info object, it will be unloaded and replaced.
- */
-this.updateAppInfo = function (obj) {
-  obj = obj || APP_INFO;
-  APP_INFO = obj;
-
-  let id = Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}");
-  let cid = "@mozilla.org/xre/app-info;1";
-  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-
-  // Unregister an existing factory if one exists.
-  try {
-    let existing = Components.manager.getClassObjectByContractID(cid, Ci.nsIFactory);
-    registrar.unregisterFactory(id, existing);
-  } catch (ex) {}
-
-  let factory = {
-    createInstance: function (outer, iid) {
-      if (outer != null) {
-        throw Cr.NS_ERROR_NO_AGGREGATION;
-      }
-
-      return obj.QueryInterface(iid);
-    },
-  };
-
-  registrar.registerFactory(id, "XULAppInfo", cid, factory);
-};
-
-/**
- * Creates a fake crash in the Crash Reports directory.
- *
- * Currently, we just create a dummy file. A more robust implementation would
- * create something that actually resembles a crash report file.
- *
- * This is very similar to code in crashreporter/tests/browser/head.js.
- *
- * FUTURE consolidate code in a shared JSM.
- */
-this.createFakeCrash = function (submitted=false, date=new Date()) {
-  let id = CommonUtils.generateUUID();
-  let filename;
-
-  let paths = ["Crash Reports"];
-  let mode;
-
-  if (submitted) {
-    paths.push("submitted");
-    filename = "bp-" + id + ".txt";
-    mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
-           OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH;
-  } else {
-    paths.push("pending");
-    filename = id + ".dmp";
-    mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
-  }
-
-  paths.push(filename);
-
-  let file = FileUtils.getFile("UAppData", paths, true);
-  file.create(file.NORMAL_FILE_TYPE, mode);
-  file.lastModifiedTime = date.getTime();
-  dump("Created fake crash: " + id + "\n");
-
-  return id;
-};
-
-
-/**
- * A HealthReporter that is probed with various callbacks and counters.
- *
- * The purpose of this type is to aid testing of startup and shutdown.
- */
-this.InspectedHealthReporter = function (branch, policy, stateLeaf) {
-  HealthReporter.call(this, branch, policy, stateLeaf);
-
-  this.onStorageCreated = null;
-  this.onProviderManagerInitialized = null;
-  this.providerManagerShutdownCount = 0;
-  this.storageCloseCount = 0;
-}
-
-InspectedHealthReporter.prototype = {
-  __proto__: HealthReporter.prototype,
-
-  _onStorageCreated: function (storage) {
-    if (this.onStorageCreated) {
-      this.onStorageCreated(storage);
-    }
-
-    return HealthReporter.prototype._onStorageCreated.call(this, storage);
-  },
-
-  _initializeProviderManager: Task.async(function* () {
-    yield HealthReporter.prototype._initializeProviderManager.call(this);
-
-    if (this.onInitializeProviderManagerFinished) {
-      this.onInitializeProviderManagerFinished();
-    }
-  }),
-
-  _onProviderManagerInitialized: function () {
-    if (this.onProviderManagerInitialized) {
-      this.onProviderManagerInitialized();
-    }
-
-    return HealthReporter.prototype._onProviderManagerInitialized.call(this);
-  },
-
-  _onProviderManagerShutdown: function () {
-    this.providerManagerShutdownCount++;
-
-    return HealthReporter.prototype._onProviderManagerShutdown.call(this);
-  },
-
-  _onStorageClose: function () {
-    this.storageCloseCount++;
-
-    return HealthReporter.prototype._onStorageClose.call(this);
-  },
-};
-
-const DUMMY_URI="http://localhost:62013/";
-
-this.getHealthReporter = function (name, uri=DUMMY_URI, inspected=false) {
-  let branch = "healthreport.testing." + name + ".";
-
-  let prefs = new Preferences(branch + "healthreport.");
-  prefs.set("documentServerURI", uri);
-  prefs.set("dbName", name);
-
-  let reporter;
-
-  let policyPrefs = new Preferences(branch + "policy.");
-  let policy = {};
-  let type = inspected ? InspectedHealthReporter : HealthReporter;
-  reporter = new type(branch + "healthreport.", policy,
-                      "state-" + name + ".json");
-
-  return reporter;
-};
deleted file mode 100644
--- a/services/healthreport/moz.build
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-SPHINX_TREES['healthreport'] = 'docs'
-
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
-
-EXTRA_PP_COMPONENTS += [
-    'HealthReportComponents.manifest',
-]
-
-EXTRA_PP_JS_MODULES += [
-    'HealthReport.jsm',
-]
-
-EXTRA_PP_JS_MODULES.services.healthreport += [
-    'healthreporter.jsm',
-    'profile.jsm',
-    'providers.jsm',
-]
-
-TESTING_JS_MODULES.services.healthreport += [
-    'modules-testing/utils.jsm',
-]
deleted file mode 100644
--- a/services/healthreport/profile.jsm
+++ /dev/null
@@ -1,124 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MERGED_COMPARTMENT
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["ProfileMetadataProvider"];
-
-const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
-
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-
-#endif
-
-const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
-const DEFAULT_PROFILE_MEASUREMENT_VERSION = 2;
-const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/osfile.jsm")
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/ProfileAge.jsm");
-
-/**
- * Measurements pertaining to the user's profile.
- */
-// This is "version 1" of the metadata measurement - it must remain, but
-// it's currently unused - see bug 1063714 comment 12 for why.
-function ProfileMetadataMeasurement() {
-  Metrics.Measurement.call(this);
-}
-ProfileMetadataMeasurement.prototype = {
-  __proto__: Metrics.Measurement.prototype,
-
-  name: DEFAULT_PROFILE_MEASUREMENT_NAME,
-  version: 1,
-
-  fields: {
-    // Profile creation date. Number of days since Unix epoch.
-    profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
-  },
-};
-
-// This is the current measurement - it adds the profileReset value.
-function ProfileMetadataMeasurement2() {
-  Metrics.Measurement.call(this);
-}
-ProfileMetadataMeasurement2.prototype = {
-  __proto__: Metrics.Measurement.prototype,
-
-  name: DEFAULT_PROFILE_MEASUREMENT_NAME,
-  version: DEFAULT_PROFILE_MEASUREMENT_VERSION,
-
-  fields: {
-    // Profile creation date. Number of days since Unix epoch.
-    profileCreation: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
-    // Profile reset date. Number of days since Unix epoch.
-    profileReset: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
-  },
-};
-
-/**
- * Turn a millisecond timestamp into a day timestamp.
- *
- * @param msec a number of milliseconds since epoch.
- * @return the number of whole days denoted by the input.
- */
-function truncate(msec) {
-  return Math.floor(msec / MILLISECONDS_PER_DAY);
-}
-
-/**
- * A Metrics.Provider for profile metadata, such as profile creation and
- * reset time.
- */
-this.ProfileMetadataProvider = function() {
-  Metrics.Provider.call(this);
-}
-this.ProfileMetadataProvider.prototype = {
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.profile",
-
-  measurementTypes: [ProfileMetadataMeasurement2],
-
-  pullOnly: true,
-
-  getProfileDays: Task.async(function* () {
-    let result = {};
-    let accessor = new ProfileAge(null, this._log);
-
-    let created = yield accessor.created;
-    result["profileCreation"] = truncate(created);
-    let reset = yield accessor.reset;
-    if (reset) {
-      result["profileReset"] = truncate(reset);
-    }
-    return result;
-  }),
-
-  collectConstantData: function () {
-    let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME,
-                                DEFAULT_PROFILE_MEASUREMENT_VERSION);
-
-    return Task.spawn(function* collectConstants() {
-      let days = yield this.getProfileDays();
-
-      yield this.enqueueStorageOperation(function storeDays() {
-        return Task.spawn(function* () {
-          yield m.setLastNumeric("profileCreation", days["profileCreation"]);
-          if (days["profileReset"]) {
-            yield m.setLastNumeric("profileReset", days["profileReset"]);
-          }
-        });
-      });
-    }.bind(this));
-  },
-};
-
deleted file mode 100644
--- a/services/healthreport/providers.jsm
+++ /dev/null
@@ -1,1792 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * This file contains metrics data providers for the Firefox Health
- * Report. Ideally each provider in this file exists in separate modules
- * and lives close to the code it is querying. However, because of the
- * overhead of JS compartments (which are created for each module), we
- * currently have all the code in one file. When the overhead of
- * compartments reaches a reasonable level, this file should be split
- * up.
- */
-
-#ifndef MERGED_COMPARTMENT
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "AddonsProvider",
-  "AppInfoProvider",
-#ifdef MOZ_CRASHREPORTER
-  "CrashesProvider",
-#endif
-  "HealthReportProvider",
-  "HotfixProvider",
-  "PlacesProvider",
-  "SearchesProvider",
-  "SessionsProvider",
-  "SysInfoProvider",
-];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-
-#endif
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/utils.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
-                                  "resource://gre/modules/AddonManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
-                                  "resource://gre/modules/UpdateUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
-                                  "resource://gre/modules/PlacesDBUtils.jsm");
-
-
-const LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_LAST_NUMERIC};
-const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT};
-const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC};
-const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
-const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
-const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
-
-const TELEMETRY_PREF = "toolkit.telemetry.enabled";
-const SEARCH_COHORT_PREF = "browser.search.cohort";
-
-function isTelemetryEnabled(prefs) {
-  return prefs.get(TELEMETRY_PREF, false);
-}
-
-/**
- * Represents basic application state.
- *
- * This is roughly a union of nsIXULAppInfo, nsIXULRuntime, with a few extra
- * pieces thrown in.
- */
-function AppInfoMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-AppInfoMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "appinfo",
-  version: 2,
-
-  fields: {
-    vendor: LAST_TEXT_FIELD,
-    name: LAST_TEXT_FIELD,
-    id: LAST_TEXT_FIELD,
-    version: LAST_TEXT_FIELD,
-    appBuildID: LAST_TEXT_FIELD,
-    platformVersion: LAST_TEXT_FIELD,
-    platformBuildID: LAST_TEXT_FIELD,
-    os: LAST_TEXT_FIELD,
-    xpcomabi: LAST_TEXT_FIELD,
-    updateChannel: LAST_TEXT_FIELD,
-    distributionID: LAST_TEXT_FIELD,
-    distributionVersion: LAST_TEXT_FIELD,
-    hotfixVersion: LAST_TEXT_FIELD,
-    locale: LAST_TEXT_FIELD,
-    isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-    isTelemetryEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-    isBlocklistEnabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-  },
-});
-
-/**
- * Legacy version of app info before Telemetry was added.
- *
- * The "last" fields have all been removed. We only report the longitudinal
- * field.
- */
-function AppInfoMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-AppInfoMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "appinfo",
-  version: 1,
-
-  fields: {
-    isDefaultBrowser: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-  },
-});
-
-
-function AppVersionMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-AppVersionMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "versions",
-  version: 1,
-
-  fields: {
-    version: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
-  },
-});
-
-// Version 2 added the build ID.
-function AppVersionMeasurement2() {
-  Metrics.Measurement.call(this);
-}
-
-AppVersionMeasurement2.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "versions",
-  version: 2,
-
-  fields: {
-    appVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
-    platformVersion: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
-    appBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
-    platformBuildID: {type: Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT},
-  },
-});
-
-/**
- * Holds data on the application update functionality.
- */
-function AppUpdateMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-AppUpdateMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "update",
-  version: 1,
-
-  fields: {
-    enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-    autoDownload: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
-  },
-});
-
-this.AppInfoProvider = function AppInfoProvider() {
-  Metrics.Provider.call(this);
-
-  this._prefs = new Preferences({defaultBranch: null});
-}
-AppInfoProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.appInfo",
-
-  measurementTypes: [
-    AppInfoMeasurement,
-    AppInfoMeasurement1,
-    AppUpdateMeasurement1,
-    AppVersionMeasurement1,
-    AppVersionMeasurement2,
-  ],
-
-  pullOnly: true,
-
-  appInfoFields: {
-    // From nsIXULAppInfo.
-    vendor: "vendor",
-    name: "name",
-    id: "ID",
-    version: "version",
-    appBuildID: "appBuildID",
-    platformVersion: "platformVersion",
-    platformBuildID: "platformBuildID",
-
-    // From nsIXULRuntime.
-    os: "OS",
-    xpcomabi: "XPCOMABI",
-  },
-
-  postInit: function () {
-    return Task.spawn(this._postInit.bind(this));
-  },
-
-  _postInit: function () {
-    let recordEmptyAppInfo = function () {
-      this._setCurrentAppVersion("");
-      this._setCurrentPlatformVersion("");
-      this._setCurrentAppBuildID("");
-      return this._setCurrentPlatformBuildID("");
-    }.bind(this);
-
-    // Services.appInfo should always be defined for any reasonably behaving
-    // Gecko app. If it isn't, we insert a empty string sentinel value.
-    let ai;
-    try {
-      ai = Services.appinfo;
-    } catch (ex) {
-      this._log.error("Could not obtain Services.appinfo", ex);
-      yield recordEmptyAppInfo();
-      return;
-    }
-
-    if (!ai) {
-      this._log.error("Services.appinfo is unavailable.");
-      yield recordEmptyAppInfo();
-      return;
-    }
-
-    let currentAppVersion = ai.version;
-    let currentPlatformVersion = ai.platformVersion;
-    let currentAppBuildID = ai.appBuildID;
-    let currentPlatformBuildID = ai.platformBuildID;
-
-    // State's name doesn't contain "app" for historical compatibility.
-    let lastAppVersion = yield this.getState("lastVersion");
-    let lastPlatformVersion = yield this.getState("lastPlatformVersion");
-    let lastAppBuildID = yield this.getState("lastAppBuildID");
-    let lastPlatformBuildID = yield this.getState("lastPlatformBuildID");
-
-    if (currentAppVersion != lastAppVersion) {
-      yield this._setCurrentAppVersion(currentAppVersion);
-    }
-
-    if (currentPlatformVersion != lastPlatformVersion) {
-      yield this._setCurrentPlatformVersion(currentPlatformVersion);
-    }
-
-    if (currentAppBuildID != lastAppBuildID) {
-      yield this._setCurrentAppBuildID(currentAppBuildID);
-    }
-
-    if (currentPlatformBuildID != lastPlatformBuildID) {
-      yield this._setCurrentPlatformBuildID(currentPlatformBuildID);
-    }
-  },
-
-  _setCurrentAppVersion: function (version) {
-    this._log.info("Recording new application version: " + version);
-    let m = this.getMeasurement("versions", 2);
-    m.addDailyDiscreteText("appVersion", version);
-
-    // "app" not encoded in key for historical compatibility.
-    return this.setState("lastVersion", version);
-  },
-
-  _setCurrentPlatformVersion: function (version) {
-    this._log.info("Recording new platform version: " + version);
-    let m = this.getMeasurement("versions", 2);
-    m.addDailyDiscreteText("platformVersion", version);
-    return this.setState("lastPlatformVersion", version);
-  },
-
-  _setCurrentAppBuildID: function (build) {
-    this._log.info("Recording new application build ID: " + build);
-    let m = this.getMeasurement("versions", 2);
-    m.addDailyDiscreteText("appBuildID", build);
-    return this.setState("lastAppBuildID", build);
-  },
-
-  _setCurrentPlatformBuildID: function (build) {
-    this._log.info("Recording new platform build ID: " + build);
-    let m = this.getMeasurement("versions", 2);
-    m.addDailyDiscreteText("platformBuildID", build);
-    return this.setState("lastPlatformBuildID", build);
-  },
-
-
-  collectConstantData: function () {
-    return this.storage.enqueueTransaction(this._populateConstants.bind(this));
-  },
-
-  _populateConstants: function () {
-    let m = this.getMeasurement(AppInfoMeasurement.prototype.name,
-                                AppInfoMeasurement.prototype.version);
-
-    let ai;
-    try {
-      ai = Services.appinfo;
-    } catch (ex) {
-      this._log.warn("Could not obtain Services.appinfo", ex);
-      throw ex;
-    }
-
-    if (!ai) {
-      this._log.warn("Services.appinfo is unavailable.");
-      throw ex;
-    }
-
-    for (let [k, v] in Iterator(this.appInfoFields)) {
-      try {
-        yield m.setLastText(k, ai[v]);
-      } catch (ex) {
-        this._log.warn("Error obtaining Services.appinfo." + v);
-      }
-    }
-
-    try {
-      yield m.setLastText("updateChannel", UpdateUtils.UpdateChannel);
-    } catch (ex) {
-      this._log.warn("Could not obtain update channel", ex);
-    }
-
-    yield m.setLastText("distributionID", this._prefs.get("distribution.id", ""));
-    yield m.setLastText("distributionVersion", this._prefs.get("distribution.version", ""));
-    yield m.setLastText("hotfixVersion", this._prefs.get("extensions.hotfix.lastVersion", ""));
-
-    try {
-      let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
-                     .getService(Ci.nsIXULChromeRegistry)
-                     .getSelectedLocale("global");
-      yield m.setLastText("locale", locale);
-    } catch (ex) {
-      this._log.warn("Could not obtain application locale", ex);
-    }
-
-    // FUTURE this should be retrieved periodically or at upload time.
-    yield this._recordIsTelemetryEnabled(m);
-    yield this._recordIsBlocklistEnabled(m);
-    yield this._recordDefaultBrowser(m);
-  },
-
-  _recordIsTelemetryEnabled: function (m) {
-    let enabled = isTelemetryEnabled(this._prefs);
-    this._log.debug("Recording telemetry enabled (" + TELEMETRY_PREF + "): " + enabled);
-    yield m.setDailyLastNumeric("isTelemetryEnabled", enabled ? 1 : 0);
-  },
-
-  _recordIsBlocklistEnabled: function (m) {
-    let enabled = this._prefs.get("extensions.blocklist.enabled", false);
-    this._log.debug("Recording blocklist enabled: " + enabled);
-    yield m.setDailyLastNumeric("isBlocklistEnabled", enabled ? 1 : 0);
-  },
-
-  _recordDefaultBrowser: function (m) {
-    let shellService;
-    try {
-      shellService = Cc["@mozilla.org/browser/shell-service;1"]
-                       .getService(Ci.nsIShellService);
-    } catch (ex) {
-      this._log.warn("Could not obtain shell service", ex);
-    }
-
-    let isDefault = -1;
-
-    if (shellService) {
-      try {
-        // This uses the same set of flags used by the pref pane.
-        isDefault = shellService.isDefaultBrowser(false, true) ? 1 : 0;
-      } catch (ex) {
-        this._log.warn("Could not determine if default browser", ex);
-      }
-    }
-
-    return m.setDailyLastNumeric("isDefaultBrowser", isDefault);
-  },
-
-  collectDailyData: function () {
-    return this.storage.enqueueTransaction(function getDaily() {
-      let m = this.getMeasurement(AppUpdateMeasurement1.prototype.name,
-                                  AppUpdateMeasurement1.prototype.version);
-
-      let enabled = this._prefs.get("app.update.enabled", false);
-      yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0);
-
-      let auto = this._prefs.get("app.update.auto", false);
-      yield m.setDailyLastNumeric("autoDownload", auto ? 1 : 0);
-    }.bind(this));
-  },
-});
-
-
-function SysInfoMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-SysInfoMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "sysinfo",
-  version: 2,
-
-  fields: {
-    cpuCount: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
-    memoryMB: {type: Metrics.Storage.FIELD_LAST_NUMERIC},
-    manufacturer: LAST_TEXT_FIELD,
-    device: LAST_TEXT_FIELD,
-    hardware: LAST_TEXT_FIELD,
-    name: LAST_TEXT_FIELD,
-    version: LAST_TEXT_FIELD,
-    architecture: LAST_TEXT_FIELD,
-    isWow64: LAST_NUMERIC_FIELD,
-  },
-});
-
-
-this.SysInfoProvider = function SysInfoProvider() {
-  Metrics.Provider.call(this);
-};
-
-SysInfoProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.sysinfo",
-
-  measurementTypes: [SysInfoMeasurement],
-
-  pullOnly: true,
-
-  sysInfoFields: {
-    cpucount: "cpuCount",
-    memsize: "memoryMB",
-    manufacturer: "manufacturer",
-    device: "device",
-    hardware: "hardware",
-    name: "name",
-    version: "version",
-    arch: "architecture",
-    isWow64: "isWow64",
-  },
-
-  collectConstantData: function () {
-    return this.storage.enqueueTransaction(this._populateConstants.bind(this));
-  },
-
-  _populateConstants: function () {
-    let m = this.getMeasurement(SysInfoMeasurement.prototype.name,
-                                SysInfoMeasurement.prototype.version);
-
-    let si = Cc["@mozilla.org/system-info;1"]
-               .getService(Ci.nsIPropertyBag2);
-
-    for (let [k, v] in Iterator(this.sysInfoFields)) {
-      try {
-        if (!si.hasKey(k)) {
-          this._log.debug("Property not available: " + k);
-          continue;
-        }
-
-        let value = si.getProperty(k);
-        let method = "setLastText";
-
-        if (["cpucount", "memsize"].indexOf(k) != -1) {
-          let converted = parseInt(value, 10);
-          if (Number.isNaN(converted)) {
-            continue;
-          }
-
-          value = converted;
-          method = "setLastNumeric";
-        }
-
-        switch (k) {
-          case "memsize":
-            // Round memory to mebibytes.
-            value = Math.round(value / 1048576);
-            break;
-          case "isWow64":
-            // Property is only present on Windows. hasKey() skipping from
-            // above ensures undefined or null doesn't creep in here.
-            value = value ? 1 : 0;
-            method = "setLastNumeric";
-            break;
-        }
-
-        yield m[method](v, value);
-      } catch (ex) {
-        this._log.warn("Error obtaining system info field: " + k, ex);
-      }
-    }
-  },
-});
-
-
-/**
- * Holds information about the current/active session.
- *
- * The fields within the current session are moved to daily session fields when
- * the application is shut down.
- *
- * This measurement is backed by the SessionRecorder, not the database.
- */
-function CurrentSessionMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-CurrentSessionMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "current",
-  version: 3,
-
-  // Storage is in preferences.
-  fields: {},
-
-  /**
-   * All data is stored in prefs, so we have a custom implementation.
-   */
-  getValues: function () {
-    let sessions = this.provider.healthReporter.sessionRecorder;
-
-    let fields = new Map();
-    let now = new Date();
-    fields.set("startDay", [now, Metrics.dateToDays(sessions.startDate)]);
-    fields.set("activeTicks", [now, sessions.activeTicks]);
-    fields.set("totalTime", [now, sessions.totalTime]);
-    fields.set("main", [now, sessions.main]);
-    fields.set("firstPaint", [now, sessions.firstPaint]);
-    fields.set("sessionRestored", [now, sessions.sessionRestored]);
-
-    return CommonUtils.laterTickResolvingPromise({
-      days: new Metrics.DailyValues(),
-      singular: fields,
-    });
-  },
-
-  _serializeJSONSingular: function (data) {
-    let result = {"_v": this.version};
-
-    for (let [field, value] of data) {
-      result[field] = value[1];
-    }
-
-    return result;
-  },
-});
-
-/**
- * Records a history of all application sessions.
- */
-function PreviousSessionsMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-PreviousSessionsMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "previous",
-  version: 3,
-
-  fields: {
-    // Milliseconds of sessions that were properly shut down.
-    cleanActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD,
-    cleanTotalTime: DAILY_DISCRETE_NUMERIC_FIELD,
-
-    // Milliseconds of sessions that were not properly shut down.
-    abortedActiveTicks: DAILY_DISCRETE_NUMERIC_FIELD,
-    abortedTotalTime: DAILY_DISCRETE_NUMERIC_FIELD,
-
-    // Startup times in milliseconds.
-    main: DAILY_DISCRETE_NUMERIC_FIELD,
-    firstPaint: DAILY_DISCRETE_NUMERIC_FIELD,
-    sessionRestored: DAILY_DISCRETE_NUMERIC_FIELD,
-  },
-});
-
-
-/**
- * Records information about the current browser session.
- *
- * A browser session is defined as an application/process lifetime. We
- * start a new session when the application starts (essentially when
- * this provider is instantiated) and end the session on shutdown.
- *
- * As the application runs, we record basic information about the
- * "activity" of the session. Activity is defined by the presence of
- * physical input into the browser (key press, mouse click, touch, etc).
- *
- * We differentiate between regular sessions and "aborted" sessions. An
- * aborted session is one that does not end expectedly. This is often the
- * result of a crash. We detect aborted sessions by storing the current
- * session separate from completed sessions. We normally move the
- * current session to completed sessions on application shutdown. If a
- * current session is present on application startup, that means that
- * the previous session was aborted.
- */
-this.SessionsProvider = function () {
-  Metrics.Provider.call(this);
-};
-
-SessionsProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.appSessions",
-
-  measurementTypes: [CurrentSessionMeasurement, PreviousSessionsMeasurement],
-
-  pullOnly: true,
-
-  collectConstantData: function () {
-    let previous = this.getMeasurement("previous", 3);
-
-    return this.storage.enqueueTransaction(this._recordAndPruneSessions.bind(this));
-  },
-
-  _recordAndPruneSessions: function () {
-    this._log.info("Moving previous sessions from session recorder to storage.");
-    let recorder = this.healthReporter.sessionRecorder;
-    let sessions = recorder.getPreviousSessions();
-    this._log.debug("Found " + Object.keys(sessions).length + " previous sessions.");
-
-    let daily = this.getMeasurement("previous", 3);
-
-    // Please note the coupling here between the session recorder and our state.
-    // If the pruned index or the current index of the session recorder is ever
-    // deleted or reset to 0, our stored state of a later index would mean that
-    // new sessions would never be captured by this provider until the session
-    // recorder index catches up to our last session ID. This should not happen
-    // under normal circumstances, so we don't worry too much about it. We
-    // should, however, consider this as part of implementing bug 841561.
-    let lastRecordedSession = yield this.getState("lastSession");
-    if (lastRecordedSession === null) {
-      lastRecordedSession = -1;
-    }
-    this._log.debug("The last recorded session was #" + lastRecordedSession);
-
-    for (let [index, session] in Iterator(sessions)) {
-      if (index <= lastRecordedSession) {
-        this._log.warn("Already recorded session " + index + ". Did the last " +
-                       "session crash or have an issue saving the prefs file?");
-        continue;
-      }
-
-      let type = session.clean ? "clean" : "aborted";
-      let date = session.startDate;
-      yield daily.addDailyDiscreteNumeric(type + "ActiveTicks", session.activeTicks, date);
-      yield daily.addDailyDiscreteNumeric(type + "TotalTime", session.totalTime, date);
-
-      for (let field of ["main", "firstPaint", "sessionRestored"]) {
-        yield daily.addDailyDiscreteNumeric(field, session[field], date);
-      }
-
-      lastRecordedSession = index;
-    }
-
-    yield this.setState("lastSession", "" + lastRecordedSession);
-    recorder.pruneOldSessions(new Date());
-  },
-});
-
-/**
- * Stores the set of active addons in storage.
- *
- * We do things a little differently than most other measurements. Because
- * addons are difficult to shoehorn into distinct fields, we simply store a
- * JSON blob in storage in a text field.
- */
-function ActiveAddonsMeasurement() {
-  Metrics.Measurement.call(this);
-
-  this._serializers = {};
-  this._serializers[this.SERIALIZE_JSON] = {
-    singular: this._serializeJSONSingular.bind(this),
-    // We don't need a daily serializer because we have none of this data.
-  };
-}
-
-ActiveAddonsMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "addons",
-  version: 2,
-
-  fields: {
-    addons: LAST_TEXT_FIELD,
-  },
-
-  _serializeJSONSingular: function (data) {
-    if (!data.has("addons")) {
-      this._log.warn("Don't have addons info. Weird.");
-      return null;
-    }
-
-    // Exceptions are caught in the caller.
-    let result = JSON.parse(data.get("addons")[1]);
-    result._v = this.version;
-    return result;
-  },
-});
-
-/**
- * Stores the set of active plugins in storage.
- *
- * This stores the data in a JSON blob in a text field similar to the
- * ActiveAddonsMeasurement.
- */
-function ActivePluginsMeasurement() {
-  Metrics.Measurement.call(this);
-
-  this._serializers = {};
-  this._serializers[this.SERIALIZE_JSON] = {
-    singular: this._serializeJSONSingular.bind(this),
-    // We don't need a daily serializer because we have none of this data.
-  };
-}
-
-ActivePluginsMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "plugins",
-  version: 1,
-
-  fields: {
-    plugins: LAST_TEXT_FIELD,
-  },
-
-  _serializeJSONSingular: function (data) {
-    if (!data.has("plugins")) {
-      this._log.warn("Don't have plugins info. Weird.");
-      return null;
-    }
-
-    // Exceptions are caught in the caller.
-    let result = JSON.parse(data.get("plugins")[1]);
-    result._v = this.version;
-    return result;
-  },
-});
-
-function ActiveGMPluginsMeasurement() {
-  Metrics.Measurement.call(this);
-
-  this._serializers = {};
-  this._serializers[this.SERIALIZE_JSON] = {
-    singular: this._serializeJSONSingular.bind(this),
-  };
-}
-
-ActiveGMPluginsMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "gm-plugins",
-  version: 1,
-
-  fields: {
-    "gm-plugins": LAST_TEXT_FIELD,
-  },
-
-  _serializeJSONSingular: function (data) {
-    if (!data.has("gm-plugins")) {
-      this._log.warn("Don't have GM plugins info. Weird.");
-      return null;
-    }
-
-    let result = JSON.parse(data.get("gm-plugins")[1]);
-    result._v = this.version;
-    return result;
-  },
-});
-
-function AddonCountsMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-AddonCountsMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "counts",
-  version: 2,
-
-  fields: {
-    theme: DAILY_LAST_NUMERIC_FIELD,
-    lwtheme: DAILY_LAST_NUMERIC_FIELD,
-    plugin: DAILY_LAST_NUMERIC_FIELD,
-    extension: DAILY_LAST_NUMERIC_FIELD,
-    service: DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-
-/**
- * Legacy version of addons counts before services was added.
- */
-function AddonCountsMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-AddonCountsMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "counts",
-  version: 1,
-
-  fields: {
-    theme: DAILY_LAST_NUMERIC_FIELD,
-    lwtheme: DAILY_LAST_NUMERIC_FIELD,
-    plugin: DAILY_LAST_NUMERIC_FIELD,
-    extension: DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-
-this.AddonsProvider = function () {
-  Metrics.Provider.call(this);
-
-  this._prefs = new Preferences({defaultBranch: null});
-};
-
-AddonsProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  // Whenever these AddonListener callbacks are called, we repopulate
-  // and store the set of addons. Note that these events will only fire
-  // for restartless add-ons. For actions that require a restart, we
-  // will catch the change after restart. The alternative is a lot of
-  // state tracking here, which isn't desirable.
-  ADDON_LISTENER_CALLBACKS: [
-    "onEnabled",
-    "onDisabled",
-    "onInstalled",
-    "onUninstalled",
-  ],
-
-  // Add-on types for which full details are uploaded in the
-  // ActiveAddonsMeasurement. All other types are ignored.
-  FULL_DETAIL_TYPES: [
-    "extension",
-    "service",
-  ],
-
-  name: "org.mozilla.addons",
-
-  measurementTypes: [
-    ActiveAddonsMeasurement,
-    ActivePluginsMeasurement,
-    ActiveGMPluginsMeasurement,
-    AddonCountsMeasurement1,
-    AddonCountsMeasurement,
-  ],
-
-  postInit: function () {
-    let listener = {};
-
-    for (let method of this.ADDON_LISTENER_CALLBACKS) {
-      listener[method] = this._collectAndStoreAddons.bind(this);
-    }
-
-    this._listener = listener;
-    AddonManager.addAddonListener(this._listener);
-
-    return CommonUtils.laterTickResolvingPromise();
-  },
-
-  onShutdown: function () {
-    AddonManager.removeAddonListener(this._listener);
-    this._listener = null;
-
-    return CommonUtils.laterTickResolvingPromise();
-  },
-
-  collectConstantData: function () {
-    return this._collectAndStoreAddons();
-  },
-
-  _collectAndStoreAddons: function () {
-    let deferred = Promise.defer();
-
-    AddonManager.getAllAddons(function onAllAddons(allAddons) {
-      let data;
-      let addonsField;
-      let pluginsField;
-      let gmPluginsField;
-      try {
-        data = this._createDataStructure(allAddons);
-        addonsField = JSON.stringify(data.addons);
-        pluginsField = JSON.stringify(data.plugins);
-        gmPluginsField = JSON.stringify(data.gmPlugins);
-      } catch (ex) {
-        this._log.warn("Exception when populating add-ons data structure", ex);
-        deferred.reject(ex);
-        return;
-      }
-
-      let now = new Date();
-      let addons = this.getMeasurement("addons", 2);
-      let plugins = this.getMeasurement("plugins", 1);
-      let gmPlugins = this.getMeasurement("gm-plugins", 1);
-      let counts = this.getMeasurement(AddonCountsMeasurement.prototype.name,
-                                       AddonCountsMeasurement.prototype.version);
-
-      this.enqueueStorageOperation(function storageAddons() {
-        for (let type in data.counts) {
-          try {
-            counts.fieldID(type);
-          } catch (ex) {
-            this._log.warn("Add-on type without field: " + type);
-            continue;
-          }
-
-          counts.setDailyLastNumeric(type, data.counts[type], now);
-        }
-
-        return addons.setLastText("addons", addonsField).then(
-          function onSuccess() {
-            return plugins.setLastText("plugins", pluginsField).then(
-              function onSuccess() {
-                return gmPlugins.setLastText("gm-plugins", gmPluginsField).then(
-                  function onSuccess() {
-                    deferred.resolve();
-                  },
-                  function onError(error) {
-                    deferred.reject(error);
-                  });
-              },
-              function onError(error) { deferred.reject(error); }
-            );
-          },
-          function onError(error) { deferred.reject(error); }
-        );
-      }.bind(this));
-    }.bind(this));
-
-    return deferred.promise;
-  },
-
-  COPY_ADDON_FIELDS: [
-    "userDisabled",
-    "appDisabled",
-    "name",
-    "version",
-    "type",
-    "scope",
-    "description",
-    "foreignInstall",
-    "hasBinaryComponents",
-  ],
-
-  COPY_PLUGIN_FIELDS: [
-    "name",
-    "version",
-    "description",
-    "blocklisted",
-    "disabled",
-    "clicktoplay",
-  ],
-
-  _createDataStructure: function (addons) {
-    let data = {
-      addons: {},
-      plugins: {},
-      gmPlugins: {},
-      counts: {}
-    };
-
-    for (let addon of addons) {
-      let type = addon.type;
-
-      // We count plugins separately below.
-      if (addon.type == "plugin") {
-        if (addon.isGMPlugin) {
-          data.gmPlugins[addon.id] = {
-            version: addon.version,
-            userDisabled: addon.userDisabled,
-            applyBackgroundUpdates: addon.applyBackgroundUpdates,
-          };
-        }
-        continue;
-      }
-
-      data.counts[type] = (data.counts[type] || 0) + 1;
-
-      if (this.FULL_DETAIL_TYPES.indexOf(addon.type) == -1) {
-        continue;
-      }
-
-      let obj = {};
-      for (let field of this.COPY_ADDON_FIELDS) {
-        obj[field] = addon[field];
-      }
-
-      if (addon.installDate) {
-        obj.installDay = this._dateToDays(addon.installDate);
-      }
-
-      if (addon.updateDate) {
-        obj.updateDay = this._dateToDays(addon.updateDate);
-      }
-
-      data.addons[addon.id] = obj;
-    }
-
-    let pluginTags = Cc["@mozilla.org/plugin/host;1"].
-                       getService(Ci.nsIPluginHost).
-                       getPluginTags({});
-
-    for (let tag of pluginTags) {
-      let obj = {
-        mimeTypes: tag.getMimeTypes({}),
-      };
-
-      for (let field of this.COPY_PLUGIN_FIELDS) {
-        obj[field] = tag[field];
-      }
-
-      // Plugins need to have a filename and a name, so this can't be empty.
-      let id = tag.filename + ":" + tag.name + ":" + tag.version + ":"
-               + tag.description;
-      data.plugins[id] = obj;
-    }
-
-    data.counts["plugin"] = pluginTags.length;
-
-    return data;
-  },
-});
-
-#ifdef MOZ_CRASHREPORTER
-
-function DailyCrashesMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 1,
-
-  fields: {
-    pending: DAILY_COUNTER_FIELD,
-    submitted: DAILY_COUNTER_FIELD,
-  },
-});
-
-function DailyCrashesMeasurement2() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement2.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 2,
-
-  fields: {
-    mainCrash: DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-function DailyCrashesMeasurement3() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement3.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 3,
-
-  fields: {
-    "main-crash": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-function DailyCrashesMeasurement4() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement4.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 4,
-
-  fields: {
-    "main-crash": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-function DailyCrashesMeasurement5() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement5.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 5,
-
-  fields: {
-    "main-crash": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-function DailyCrashesMeasurement6() {
-  Metrics.Measurement.call(this);
-}
-
-DailyCrashesMeasurement6.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "crashes",
-  version: 6,
-
-  fields: {
-    "main-crash": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-oom": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
-    "gmplugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-this.CrashesProvider = function () {
-  Metrics.Provider.call(this);
-
-  // So we can unit test.
-  this._manager = Services.crashmanager;
-};
-
-CrashesProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.crashes",
-
-  measurementTypes: [
-    DailyCrashesMeasurement1,
-    DailyCrashesMeasurement2,
-    DailyCrashesMeasurement3,
-    DailyCrashesMeasurement4,
-    DailyCrashesMeasurement5,
-    DailyCrashesMeasurement6,
-  ],
-
-  pullOnly: true,
-
-  collectDailyData: function () {
-    return this.storage.enqueueTransaction(this._populateCrashCounts.bind(this));
-  },
-
-  _populateCrashCounts: function () {
-    this._log.info("Grabbing crash counts from crash manager.");
-    let crashCounts = yield this._manager.getCrashCountsByDay();
-
-    // TODO: CrashManager no longer stores submissions as crashes, but we still
-    // want to send the submission data to FHR. As a temporary workaround, we
-    // populate |crashCounts| with the submission data to match past behaviour.
-    // See bug 1056160.
-    let crashes = yield this._manager.getCrashes();
-    for (let crash of crashes) {
-      for (let [submissionID, submission] of crash.submissions) {
-        if (!submission.responseDate) {
-          continue;
-        }
-
-        let day = Metrics.dateToDays(submission.responseDate);
-        if (!crashCounts.has(day)) {
-          crashCounts.set(day, new Map());
-        }
-
-        let succeeded =
-          submission.result == this._manager.SUBMISSION_RESULT_OK;
-        let type = crash.type + "-submission-" + (succeeded ? "succeeded" :
-                                                              "failed");
-
-        let count = (crashCounts.get(day).get(type) || 0) + 1;
-        crashCounts.get(day).set(type, count);
-      }
-    }
-
-    let m = this.getMeasurement("crashes", 6);
-    let fields = DailyCrashesMeasurement6.prototype.fields;
-
-    for (let [day, types] of crashCounts) {
-      let date = Metrics.daysToDate(day);
-      for (let [type, count] of types) {
-        if (!(type in fields)) {
-          this._log.warn("Unknown crash type encountered: " + type);
-          continue;
-        }
-
-        yield m.setDailyLastNumeric(type, count, date);
-      }
-    }
-  },
-});
-
-#endif
-
-/**
- * Records data from update hotfixes.
- *
- * This measurement has dynamic fields. Field names are of the form
- * <version>.<thing> where <version> is the hotfix version that produced
- * the data. e.g. "v20140527". The sub-version of the hotfix is omitted
- * because hotfixes can go through multiple minor versions during development
- * and we don't want to introduce more fields than necessary. Furthermore,
- * the subsequent dots make parsing field names slightly harder. By stripping,
- * we can just split on the first dot.
- */
-function UpdateHotfixMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-UpdateHotfixMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "update",
-  version: 1,
-
-  hotfixFieldTypes: {
-    "upgradedFrom": Metrics.Storage.FIELD_LAST_TEXT,
-    "uninstallReason": Metrics.Storage.FIELD_LAST_TEXT,
-    "downloadAttempts": Metrics.Storage.FIELD_LAST_NUMERIC,
-    "downloadFailures": Metrics.Storage.FIELD_LAST_NUMERIC,
-    "installAttempts": Metrics.Storage.FIELD_LAST_NUMERIC,
-    "installFailures": Metrics.Storage.FIELD_LAST_NUMERIC,
-    "notificationsShown": Metrics.Storage.FIELD_LAST_NUMERIC,
-  },
-
-  fields: { },
-
-  // Our fields have dynamic names from the hotfix version that supplied them.
-  // We need to override the default behavior to deal with unknown fields.
-  shouldIncludeField: function (name) {
-    return name.includes(".");
-  },
-
-  fieldType: function (name) {
-    for (let known in this.hotfixFieldTypes) {
-      if (name.endsWith(known)) {
-        return this.hotfixFieldTypes[known];
-      }
-    }
-
-    return Metrics.Measurement.prototype.fieldType.call(this, name);
-  },
-});
-
-this.HotfixProvider = function () {
-  Metrics.Provider.call(this);
-};
-
-HotfixProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.hotfix",
-  measurementTypes: [
-    UpdateHotfixMeasurement1,
-  ],
-
-  pullOnly: true,
-
-  collectDailyData: function () {
-    return this.storage.enqueueTransaction(this._populateHotfixData.bind(this));
-  },
-
-  _populateHotfixData: function* () {
-    let m = this.getMeasurement("update", 1);
-
-    // The update hotfix retains its JSON state file after uninstall.
-    // The initial update hotfix had a hard-coded filename. We treat it
-    // specially. Subsequent update hotfixes named their files in a
-    // recognizeable pattern so we don't need to update this probe code to
-    // know about them.
-    let files = [
-        ["v20140527", OS.Path.join(OS.Constants.Path.profileDir,
-                                   "hotfix.v20140527.01.json")],
-    ];
-
-    let it = new OS.File.DirectoryIterator(OS.Constants.Path.profileDir);
-    try {
-      yield it.forEach((e, index, it) => {
-        let m = e.name.match(/^updateHotfix\.([a-zA-Z0-9]+)\.json$/);
-        if (m) {
-          files.push([m[1], e.path]);
-        }
-      });
-    } finally {
-      it.close();
-    }
-
-    let decoder = new TextDecoder();
-    for (let e of files) {
-      let [version, path] = e;
-      let p;
-      try {
-        let data = yield OS.File.read(path);
-        p = JSON.parse(decoder.decode(data));
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-        continue;
-      } catch (ex) {
-        this._log.warn("Error loading update hotfix payload: " + ex.message);
-      }
-
-      // Wrap just in case.
-      try {
-        for (let k in m.hotfixFieldTypes) {
-          if (!(k in p)) {
-            continue;
-          }
-
-          let value = p[k];
-          if (value === null && k == "uninstallReason") {
-            value = "STILL_INSTALLED";
-          }
-
-          let field = version + "." + k;
-          let fieldType;
-          let storageOp;
-          switch (typeof(value)) {
-            case "string":
-              fieldType = this.storage.FIELD_LAST_TEXT;
-              storageOp = "setLastTextFromFieldID";
-              break;
-            case "number":
-              fieldType = this.storage.FIELD_LAST_NUMERIC;
-              storageOp = "setLastNumericFromFieldID";
-              break;
-            default:
-              this._log.warn("Unknown value in hotfix state: " + k + "=" + value);
-              continue;
-          }
-
-          if (this.storage.hasFieldFromMeasurement(m.id, field, fieldType)) {
-            let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
-            yield this.storage[storageOp](fieldID, value);
-          } else {
-            let fieldID = yield this.storage.registerField(m.id, field,
-                                                           fieldType);
-            yield this.storage[storageOp](fieldID, value);
-          }
-        }
-
-      } catch (ex) {
-        this._log.warn("Error processing update hotfix data: " + ex);
-      }
-    }
-  },
-});
-
-/**
- * Holds basic statistics about the Places database.
- */
-function PlacesMeasurement() {
-  Metrics.Measurement.call(this);
-}
-
-PlacesMeasurement.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "places",
-  version: 1,
-
-  fields: {
-    pages: DAILY_LAST_NUMERIC_FIELD,
-    bookmarks: DAILY_LAST_NUMERIC_FIELD,
-  },
-});
-
-
-/**
- * Collects information about Places.
- */
-this.PlacesProvider = function () {
-  Metrics.Provider.call(this);
-};
-
-PlacesProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.places",
-
-  measurementTypes: [PlacesMeasurement],
-
-  collectDailyData: function () {
-    return this.storage.enqueueTransaction(this._collectData.bind(this));
-  },
-
-  _collectData: function () {
-    let now = new Date();
-    let data = yield this._getDailyValues();
-
-    let m = this.getMeasurement("places", 1);
-
-    yield m.setDailyLastNumeric("pages", data.PLACES_PAGES_COUNT);
-    yield m.setDailyLastNumeric("bookmarks", data.PLACES_BOOKMARKS_COUNT);
-  },
-
-  _getDailyValues: function () {
-    let deferred = Promise.defer();
-
-    PlacesDBUtils.telemetry(null, function onResult(data) {
-      deferred.resolve(data);
-    });
-
-    return deferred.promise;
-  },
-});
-
-function SearchCountMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-SearchCountMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "counts",
-  version: 1,
-
-  // We only record searches for search engines that have partner agreements
-  // with Mozilla.
-  fields: {
-    "amazon.com.abouthome": DAILY_COUNTER_FIELD,
-    "amazon.com.contextmenu": DAILY_COUNTER_FIELD,
-    "amazon.com.searchbar": DAILY_COUNTER_FIELD,
-    "amazon.com.urlbar": DAILY_COUNTER_FIELD,
-    "bing.abouthome": DAILY_COUNTER_FIELD,
-    "bing.contextmenu": DAILY_COUNTER_FIELD,
-    "bing.searchbar": DAILY_COUNTER_FIELD,
-    "bing.urlbar": DAILY_COUNTER_FIELD,
-    "google.abouthome": DAILY_COUNTER_FIELD,
-    "google.contextmenu": DAILY_COUNTER_FIELD,
-    "google.searchbar": DAILY_COUNTER_FIELD,
-    "google.urlbar": DAILY_COUNTER_FIELD,
-    "yahoo.abouthome": DAILY_COUNTER_FIELD,
-    "yahoo.contextmenu": DAILY_COUNTER_FIELD,
-    "yahoo.searchbar": DAILY_COUNTER_FIELD,
-    "yahoo.urlbar": DAILY_COUNTER_FIELD,
-    "other.abouthome": DAILY_COUNTER_FIELD,
-    "other.contextmenu": DAILY_COUNTER_FIELD,
-    "other.searchbar": DAILY_COUNTER_FIELD,
-    "other.urlbar": DAILY_COUNTER_FIELD,
-  },
-});
-
-/**
- * Records search counts per day per engine and where search initiated.
- *
- * We want to record granular details for individual locale-specific search
- * providers, but only if they're Mozilla partners. In order to do this, we
- * track the nsISearchEngine identifier, which denotes shipped search engines,
- * and intersect those with our partner list.
- *
- * We don't use the search engine name directly, because it is shared across
- * locales; e.g., eBay-de and eBay both share the name "eBay".
- */
-function SearchCountMeasurementBase() {
-  this._fieldSpecs = {};
-  Metrics.Measurement.call(this);
-}
-
-SearchCountMeasurementBase.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-
-  // Our fields are dynamic.
-  get fields() {
-    return this._fieldSpecs;
-  },
-
-  /**
-   * Override the default behavior: serializers should include every counter
-   * field from the DB, even if we don't currently have it registered.
-   *
-   * Do this so we don't have to register several hundred fields to match
-   * various Firefox locales.
-   *
-   * We use the "provider.type" syntax as a rudimentary check for validity.
-   *
-   * We trust that measurement versioning is sufficient to exclude old provider
-   * data.
-   */
-  shouldIncludeField: function (name) {
-    return name.includes(".");
-  },
-
-  /**
-   * The measurement type mechanism doesn't introspect the DB. Override it
-   * so that we can assume all unknown fields are counters.
-   */
-  fieldType: function (name) {
-    if (name in this.fields) {
-      return this.fields[name].type;
-    }
-
-    // Default to a counter.
-    return Metrics.Storage.FIELD_DAILY_COUNTER;
-  },
-
-  SOURCES: [
-    "abouthome",
-    "contextmenu",
-    "newtab",
-    "searchbar",
-    "urlbar",
-  ],
-});
-
-function SearchCountMeasurement2() {
-  SearchCountMeasurementBase.call(this);
-}
-
-SearchCountMeasurement2.prototype = Object.freeze({
-  __proto__: SearchCountMeasurementBase.prototype,
-  name: "counts",
-  version: 2,
-});
-
-function SearchCountMeasurement3() {
-  SearchCountMeasurementBase.call(this);
-}
-
-SearchCountMeasurement3.prototype = Object.freeze({
-  __proto__: SearchCountMeasurementBase.prototype,
-  name: "counts",
-  version: 3,
-
-  getEngines: function () {
-    return Services.search.getEngines();
-  },
-
-  getEngineID: function (engine) {
-    if (!engine) {
-      return "other";
-    }
-    if (engine.identifier) {
-      return engine.identifier;
-    }
-    return "other-" + engine.name;
-  },
-});
-
-function SearchEnginesMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-SearchEnginesMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "engines",
-  version: 2,
-
-  fields: {
-    default: DAILY_LAST_TEXT_FIELD,
-    cohort: DAILY_LAST_TEXT_FIELD,
-  },
-});
-
-this.SearchesProvider = function () {
-  Metrics.Provider.call(this);
-
-  this._prefs = new Preferences({defaultBranch: null});
-};
-
-this.SearchesProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.searches",
-  measurementTypes: [
-    SearchCountMeasurement1,
-    SearchCountMeasurement2,
-    SearchCountMeasurement3,
-    SearchEnginesMeasurement1,
-  ],
-
-  /**
-   * Initialize the search service before our measurements are touched.
-   */
-  preInit: function (storage) {
-    // Initialize search service.
-    let deferred = Promise.defer();
-    Services.search.init(function onInitComplete () {
-      deferred.resolve();
-    });
-    return deferred.promise;
-  },
-
-  collectDailyData: function () {
-    return this.storage.enqueueTransaction(function getDaily() {
-      let m = this.getMeasurement(SearchEnginesMeasurement1.prototype.name,
-                                  SearchEnginesMeasurement1.prototype.version);
-
-      let engine;
-      try {
-        engine = Services.search.defaultEngine;
-      } catch (e) {}
-      let name;
-
-      if (!engine) {
-        name = "NONE";
-      } else if (engine.identifier) {
-        name = engine.identifier;
-      } else if (engine.name) {
-        name = "other-" + engine.name;
-      } else {
-        name = "UNDEFINED";
-      }
-
-      yield m.setDailyLastText("default", name);
-
-      if (Services.prefs.prefHasUserValue(SEARCH_COHORT_PREF))
-        yield m.setDailyLastText("cohort", Services.prefs.getCharPref(SEARCH_COHORT_PREF));
-    }.bind(this));
-  },
-
-  /**
-   * Record that a search occurred.
-   *
-   * @param engine
-   *        (nsISearchEngine) The search engine used.
-   * @param source
-   *        (string) Where the search was initiated from. Must be one of the
-   *        SearchCountMeasurement2.SOURCES values.
-   *
-   * @return Promise<>
-   *         The promise is resolved when the storage operation completes.
-   */
-  recordSearch: function (engine, source) {
-    let m = this.getMeasurement("counts", 3);
-
-    if (m.SOURCES.indexOf(source) == -1) {
-      throw new Error("Unknown source for search: " + source);
-    }
-
-    let field = m.getEngineID(engine) + "." + source;
-    if (this.storage.hasFieldFromMeasurement(m.id, field,
-                                             this.storage.FIELD_DAILY_COUNTER)) {
-      let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
-      return this.enqueueStorageOperation(function recordSearchKnownField() {
-        return this.storage.incrementDailyCounterFromFieldID(fieldID);
-      }.bind(this));
-    }
-
-    // Otherwise, we first need to create the field.
-    return this.enqueueStorageOperation(function recordFieldAndSearch() {
-      // This function has to return a promise.
-      return Task.spawn(function () {
-        let fieldID = yield this.storage.registerField(m.id, field,
-                                                       this.storage.FIELD_DAILY_COUNTER);
-        yield this.storage.incrementDailyCounterFromFieldID(fieldID);
-      }.bind(this));
-    }.bind(this));
-  },
-});
-
-function HealthReportSubmissionMeasurement1() {
-  Metrics.Measurement.call(this);
-}
-
-HealthReportSubmissionMeasurement1.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "submissions",
-  version: 1,
-
-  fields: {
-    firstDocumentUploadAttempt: DAILY_COUNTER_FIELD,
-    continuationUploadAttempt: DAILY_COUNTER_FIELD,
-    uploadSuccess: DAILY_COUNTER_FIELD,
-    uploadTransportFailure: DAILY_COUNTER_FIELD,
-    uploadServerFailure: DAILY_COUNTER_FIELD,
-    uploadClientFailure: DAILY_COUNTER_FIELD,
-  },
-});
-
-function HealthReportSubmissionMeasurement2() {
-  Metrics.Measurement.call(this);
-}
-
-HealthReportSubmissionMeasurement2.prototype = Object.freeze({
-  __proto__: Metrics.Measurement.prototype,
-
-  name: "submissions",
-  version: 2,
-
-  fields: {
-    firstDocumentUploadAttempt: DAILY_COUNTER_FIELD,
-    continuationUploadAttempt: DAILY_COUNTER_FIELD,
-    uploadSuccess: DAILY_COUNTER_FIELD,
-    uploadTransportFailure: DAILY_COUNTER_FIELD,
-    uploadServerFailure: DAILY_COUNTER_FIELD,
-    uploadClientFailure: DAILY_COUNTER_FIELD,
-    uploadAlreadyInProgress: DAILY_COUNTER_FIELD,
-  },
-});
-
-this.HealthReportProvider = function () {
-  Metrics.Provider.call(this);
-}
-
-HealthReportProvider.prototype = Object.freeze({
-  __proto__: Metrics.Provider.prototype,
-
-  name: "org.mozilla.healthreport",
-
-  measurementTypes: [
-    HealthReportSubmissionMeasurement1,
-    HealthReportSubmissionMeasurement2,
-  ],
-
-  recordEvent: function (event, date=new Date()) {
-    let m = this.getMeasurement("submissions", 2);
-    return this.enqueueStorageOperation(function recordCounter() {
-      return m.incrementDailyCounter(event, date);
-    });
-  },
-});
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/head.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// We need to initialize the profile or OS.File may not work. See bug 810543.
-do_get_profile();
-
-(function initMetricsTestingInfrastructure() {
-  let ns = {};
-  Components.utils.import("resource://testing-common/services/common/logging.js",
-                          ns);
-
-  ns.initTestLogging();
-}).call(this);
-
-(function createAppInfo() {
-  let ns = {};
-  Components.utils.import("resource://testing-common/services/healthreport/utils.jsm", ns);
-  ns.updateAppInfo();
-}).call(this);
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_load_modules.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const modules = [
-  "healthreporter.jsm",
-  "profile.jsm",
-  "providers.jsm",
-];
-
-function run_test() {
-  for (let m of modules) {
-    let resource = "resource://gre/modules/services/healthreport/" + m;
-    Components.utils.import(resource, {});
-  }
-
-  Components.utils.import("resource://gre/modules/HealthReport.jsm", {});
-}
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_profile.js
+++ /dev/null
@@ -1,258 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
-
-// Create profile directory before use.
-// It can be no older than a day ago….
-var profile_creation_lower = Date.now() - MILLISECONDS_PER_DAY;
-do_get_profile();
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/services/healthreport/profile.jsm");
-Cu.import("resource://gre/modules/ProfileAge.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-
-function MockProfileMetadataProvider(name="MockProfileMetadataProvider") {
-  this.name = name;
-  ProfileMetadataProvider.call(this);
-}
-MockProfileMetadataProvider.prototype = {
-  __proto__: ProfileMetadataProvider.prototype,
-  includeProfileReset: false,
-
-  getProfileDays: function getProfileDays() {
-    let result = {profileCreation: 1234};
-    if (this.includeProfileReset) {
-      result.profileReset = 5678;
-    }
-    return Promise.resolve(result);
-  },
-};
-
-
-function run_test() {
-  run_next_test();
-}
-
-/**
- * Ensure that OS.File works in our environment.
- * This test can go once there are xpcshell tests for OS.File.
- */
-add_test(function use_os_file() {
-  Cu.import("resource://gre/modules/osfile.jsm")
-
-  // Ensure that we get constants, too.
-  do_check_neq(OS.Constants.Path.profileDir, null);
-
-  let iterator = new OS.File.DirectoryIterator(".");
-  iterator.forEach(function onEntry(entry) {
-    print("Got " + entry.path);
-  }).then(function onSuccess() {
-    iterator.close();
-    print("Done.");
-    run_next_test();
-  }, function onFail() {
-    iterator.close();
-    do_throw("Iterating over current directory failed.");
-  });
-});
-
-function getAccessor() {
-  let acc = new ProfileAge();
-  print("Profile is " + acc.profilePath);
-  return acc;
-}
-
-add_test(function test_time_accessor_no_file() {
-  let acc = getAccessor();
-
-  // There should be no file yet.
-  acc.readTimes()
-     .then(function onSuccess(json) {
-       do_throw("File existed!");
-     },
-     function onFailure() {
-       run_next_test();
-     });
-});
-
-add_task(function test_time_accessor_named_file() {
-  let acc = getAccessor();
-
-  // There should be no file yet.
-  yield acc.writeTimes({created: 12345}, "test.json");
-  let json = yield acc.readTimes("test.json")
-  print("Read: " + JSON.stringify(json));
-  do_check_eq(12345, json.created);
-});
-
-add_task(function test_time_accessor_creates_file() {
-  let lower = profile_creation_lower;
-
-  // Ensure that provided contents are merged, and existing
-  // files can be overwritten. These two things occur if we
-  // read and then decide that we have to write.
-  let acc = getAccessor();
-  let existing = {abc: "123", easy: "abc"};
-  let expected;
-
-  let created = yield acc.computeAndPersistCreated(existing, "test2.json")
-  let upper = Date.now() + 1000;
-  print(lower + " < " + created + " <= " + upper);
-  do_check_true(lower < created);
-  do_check_true(upper >= created);
-  expected = created;
-
-  let json = yield acc.readTimes("test2.json")
-  print("Read: " + JSON.stringify(json));
-  do_check_eq("123", json.abc);
-  do_check_eq("abc", json.easy);
-  do_check_eq(expected, json.created);
-});
-
-add_task(function test_time_accessor_all() {
-  let lower = profile_creation_lower;
-  let acc = getAccessor();
-  let expected;
-  let created = yield acc.created
-  let upper = Date.now() + 1000;
-  do_check_true(lower < created);
-  do_check_true(upper >= created);
-  expected = created;
-
-  let again = yield acc.created
-  do_check_eq(expected, again);
-});
-
-add_task(function* test_time_reset() {
-  let lower = profile_creation_lower;
-  let acc = getAccessor();
-  let testTime = 100000;
-  yield acc.recordProfileReset(testTime);
-  let reset = yield acc.reset;
-  Assert.equal(reset, testTime);
-});
-
-add_test(function test_constructor() {
-  let provider = new ProfileMetadataProvider("named");
-  run_next_test();
-});
-
-add_test(function test_profile_files() {
-  let provider = new ProfileMetadataProvider();
-
-  function onSuccess(answer) {
-    let now = Date.now() / MILLISECONDS_PER_DAY;
-    print("Got " + answer.profileCreation + ", versus now = " + now);
-    Assert.ok(answer.profileCreation < now);
-    run_next_test();
-  }
-
-  function onFailure(ex) {
-    do_throw("Directory iteration failed: " + ex);
-  }
-
-  provider.getProfileDays().then(onSuccess, onFailure);
-});
-
-// A generic test helper. We use this with both real
-// and mock providers in these tests.
-function test_collect_constant(provider, expectReset) {
-  return Task.spawn(function* () {
-    yield provider.collectConstantData();
-
-    let m = provider.getMeasurement("age", 2);
-    Assert.notEqual(m, null);
-    let values = yield m.getValues();
-    Assert.ok(values.singular.has("profileCreation"));
-    let createValue = values.singular.get("profileCreation")[1];
-    let resetValue;
-    if (expectReset) {
-      Assert.equal(values.singular.size, 2);
-      Assert.ok(values.singular.has("profileReset"));
-      resetValue = values.singular.get("profileReset")[1];
-    } else {
-      Assert.equal(values.singular.size, 1);
-      Assert.ok(!values.singular.has("profileReset"));
-    }
-    return [createValue, resetValue];
-  });
-}
-
-add_task(function* test_collect_constant_mock_no_reset() {
-  let storage = yield Metrics.Storage("collect_constant_mock");
-  let provider = new MockProfileMetadataProvider();
-  yield provider.init(storage);
-
-  let v = yield test_collect_constant(provider, false);
-  Assert.equal(v.length, 2);
-  Assert.equal(v[0], 1234);
-  Assert.equal(v[1], undefined);
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_constant_mock_with_reset() {
-  let storage = yield Metrics.Storage("collect_constant_mock");
-  let provider = new MockProfileMetadataProvider();
-  provider.includeProfileReset = true;
-  yield provider.init(storage);
-
-  let v = yield test_collect_constant(provider, true);
-  Assert.equal(v.length, 2);
-  Assert.equal(v[0], 1234);
-  Assert.equal(v[1], 5678);
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_constant_real_no_reset() {
-  let provider = new ProfileMetadataProvider();
-  let storage = yield Metrics.Storage("collect_constant_real");
-  yield provider.init(storage);
-
-  let vals = yield test_collect_constant(provider, false);
-  let created = vals[0];
-  let reset = vals[1];
-  Assert.equal(reset, undefined);
-
-  let ms = created * MILLISECONDS_PER_DAY;
-  let lower = profile_creation_lower;
-  let upper = Date.now() + 1000;
-  print("Day:   " + created);
-  print("msec:  " + ms);
-  print("Lower: " + lower);
-  print("Upper: " + upper);
-  Assert.ok(lower <= ms);
-  Assert.ok(upper >= ms);
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_constant_real_with_reset() {
-  let now = Date.now();
-  let acc = getAccessor();
-  yield acc.writeTimes({created: now-MILLISECONDS_PER_DAY, // yesterday
-                        reset: Date.now()}); // today
-
-  let provider = new ProfileMetadataProvider();
-  let storage = yield Metrics.Storage("collect_constant_real");
-  yield provider.init(storage);
-
-  let [created, reset] = yield test_collect_constant(provider, true);
-  // we've already tested truncate() works as expected, so here just check
-  // we got values.
-  Assert.ok(created);
-  Assert.ok(reset);
-  Assert.ok(created <= reset);
-
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_addons.js
+++ /dev/null
@@ -1,339 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
-
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-// The hack, it burns. This could go away if extensions code exposed its
-// test environment setup functions as a testing-only JSM. See similar
-// code in Sync's head_helpers.js.
-var gGlobalScope = this;
-function loadAddonManager() {
-  let ns = {};
-  Cu.import("resource://gre/modules/Services.jsm", ns);
-  let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
-  let file = do_get_file(head);
-  let uri = ns.Services.io.newFileURI(file);
-  ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-  startupManager();
-}
-
-function run_test() {
-  loadAddonManager();
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new AddonsProvider();
-
-  run_next_test();
-});
-
-add_task(function test_init() {
-  let storage = yield Metrics.Storage("init");
-  let provider = new AddonsProvider();
-  yield provider.init(storage);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-function monkeypatchAddons(provider, addons) {
-  if (!Array.isArray(addons)) {
-    throw new Error("Must define array of addon objects.");
-  }
-
-  Object.defineProperty(provider, "_createDataStructure", {
-    value: function _createDataStructure() {
-      return AddonsProvider.prototype._createDataStructure.call(provider, addons);
-    },
-  });
-}
-
-add_task(function test_collect() {
-  let storage = yield Metrics.Storage("collect");
-  let provider = new AddonsProvider();
-  yield provider.init(storage);
-
-  let now = new Date();
-
-  // FUTURE install add-on via AddonManager and don't use monkeypatching.
-  let testAddons = [
-    {
-      id: "addon0",
-      userDisabled: false,
-      appDisabled: false,
-      version: "1",
-      type: "extension",
-      scope: 1,
-      foreignInstall: false,
-      hasBinaryComponents: false,
-      installDate: now,
-      updateDate: now,
-    },
-    // This plugin entry should get ignored.
-    {
-      id: "addon1",
-      userDisabled: false,
-      appDisabled: false,
-      version: "2",
-      type: "plugin",
-      scope: 1,
-      foreignInstall: false,
-      hasBinaryComponents: false,
-      installDate: now,
-      updateDate: now,
-    },
-    // Is counted but full details are omitted because it is a theme.
-    {
-      id: "addon2",
-      userDisabled: false,
-      appDisabled: false,
-      version: "3",
-      type: "theme",
-      scope: 1,
-      foreignInstall: false,
-      hasBinaryComponents: false,
-      installDate: now,
-      updateDate: now,
-    },
-    {
-      id: "addon3",
-      userDisabled: false,
-      appDisabled: false,
-      version: "4",
-      type: "service",
-      scope: 1,
-      foreignInstall: false,
-      hasBinaryComponents: false,
-      installDate: now,
-      updateDate: now,
-      description: "addon3 description"
-    },
-    {
-      // Should be excluded from the report completely
-      id: "pluginfake",
-      type: "plugin",
-      userDisabled: false,
-      appDisabled: false,
-    },
-    {
-      // Should be in gm-plugins
-      id: "gmp-testgmp",
-      type: "plugin",
-      userDisabled: false,
-      version: "7.2",
-      isGMPlugin: true,
-    },
-  ];
-
-  monkeypatchAddons(provider, testAddons);
-
-  let testPlugins = {
-    "Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Plug-in for testing purposes.™ (हिन्दी 中文 العربية)",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-test"
-      ],
-    },
-    "Second Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Second plug-in for testing purposes.",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-second-test"
-      ],
-    },
-    "Java Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Dummy Java plug-in for testing purposes.",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-java-test"
-      ],
-    },
-    "Third Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Third plug-in for testing purposes.",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-third-test"
-      ],
-    },
-    "Flash Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Flash plug-in for testing purposes.",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-shockwave-flash-test"
-      ],
-    },
-    "Silverlight Test Plug-in":
-    {
-      "version": "1.0.0.0",
-      "description": "Silverlight plug-in for testing purposes.",
-      "blocklisted": false,
-      "disabled": false,
-      "clicktoplay": false,
-      "mimeTypes":[
-        "application/x-silverlight-test"
-      ],
-    },
-  };
-
-  let pluginTags = Cc["@mozilla.org/plugin/host;1"]
-                    .getService(Ci.nsIPluginHost)
-                    .getPluginTags({});
-
-  for (let tag of pluginTags) {
-    if (tag.name in testPlugins) {
-      let p = testPlugins[tag.name];
-      p.id = tag.filename+":"+tag.name+":"+p.version+":"+p.description;
-    }
-  }
-
-  yield provider.collectConstantData();
-
-  // Test addons measurement.
-
-  let addons = provider.getMeasurement("addons", 2);
-  let data = yield addons.getValues();
-
-  do_check_eq(data.days.size, 0);
-  do_check_eq(data.singular.size, 1);
-  do_check_true(data.singular.has("addons"));
-
-  let json = data.singular.get("addons")[1];
-  let value = JSON.parse(json);
-  do_check_eq(typeof(value), "object");
-  do_check_eq(Object.keys(value).length, 2);
-  do_check_true("addon0" in value);
-  do_check_true(!("addon1" in value));
-  do_check_true(!("addon2" in value));
-  do_check_true("addon3" in value);
-  do_check_true(!("pluginfake" in value));
-  do_check_true(!("gmp-testgmp" in value));
-
-  let serializer = addons.serializer(addons.SERIALIZE_JSON);
-  let serialized = serializer.singular(data.singular);
-  do_check_eq(typeof(serialized), "object");
-  do_check_eq(Object.keys(serialized).length, 3); // Our entries, plus _v.
-  do_check_true("addon0" in serialized);
-  do_check_true("addon3" in serialized);
-  do_check_eq(serialized._v, 2);
-
-  // Test plugins measurement.
-
-  let plugins = provider.getMeasurement("plugins", 1);
-  data = yield plugins.getValues();
-
-  do_check_eq(data.days.size, 0);
-  do_check_eq(data.singular.size, 1);
-  do_check_true(data.singular.has("plugins"));
-
-  json = data.singular.get("plugins")[1];
-  value = JSON.parse(json);
-  do_check_eq(typeof(value), "object");
-  do_check_eq(Object.keys(value).length, pluginTags.length);
-
-  do_check_true(testPlugins["Test Plug-in"].id in value);
-  do_check_true(testPlugins["Second Test Plug-in"].id in value);
-  do_check_true(testPlugins["Java Test Plug-in"].id in value);
-
-  for (let id in value) {
-    let item = value[id];
-    let testData = testPlugins[item.name];
-    for (let prop in testData) {
-      if (prop == "mimeTypes" || prop == "id") {
-        continue;
-      }
-      do_check_eq(testData[prop], item[prop]);
-    }
-
-    for (let mime of testData.mimeTypes) {
-      do_check_true(item.mimeTypes.indexOf(mime) != -1);
-    }
-  }
-
-  serializer = plugins.serializer(plugins.SERIALIZE_JSON);
-  serialized = serializer.singular(data.singular);
-  do_check_eq(typeof(serialized), "object");
-  do_check_eq(Object.keys(serialized).length, pluginTags.length+1); // Our entries, plus _v.
-  for (let name in testPlugins) {
-    // Special case for bug 1165981. There is a test plugin that
-    // exists to make sure we don't load it on certain platforms.
-    // We skip the check for that plugin here, as it will work on some
-    // platforms but not others.
-    if (name == "Third Test Plug-in") {
-      continue;
-    }
-    do_check_true(testPlugins[name].id in serialized);
-  }
-  do_check_eq(serialized._v, 1);
-
-  // Test GMP plugins measurement.
-
-  let gmPlugins = provider.getMeasurement("gm-plugins", 1);
-  data = yield gmPlugins.getValues();
-
-  do_check_eq(data.days.size, 0);
-  do_check_eq(data.singular.size, 1);
-  do_check_true(data.singular.has("gm-plugins"));
-
-  json = data.singular.get("gm-plugins")[1];
-  value = JSON.parse(json);
-  do_print("value: " + json);
-  do_check_eq(typeof(value), "object");
-  do_check_eq(Object.keys(value).length, 1);
-
-  do_check_eq(value["gmp-testgmp"].version, "7.2");
-  do_check_eq(value["gmp-testgmp"].userDisabled, false);
-
-  serializer = gmPlugins.serializer(plugins.SERIALIZE_JSON);
-  serialized = serializer.singular(data.singular);
-  do_check_eq(typeof(serialized), "object");
-  do_check_eq(serialized["gmp-testgmp"].version, "7.2");
-  do_check_eq(serialized._v, 1);
-
-  // Test counts measurement.
-
-  let counts = provider.getMeasurement("counts", 2);
-  data = yield counts.getValues();
-  do_check_eq(data.days.size, 1);
-  do_check_eq(data.singular.size, 0);
-  do_check_true(data.days.hasDay(now));
-
-  value = data.days.getDay(now);
-  do_check_eq(value.size, 4);
-  do_check_eq(value.get("extension"), 1);
-  do_check_eq(value.get("plugin"), pluginTags.length);
-  do_check_eq(value.get("theme"), 1);
-  do_check_eq(value.get("service"), 1);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_appinfo.js
+++ /dev/null
@@ -1,270 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {interfaces: Ci, results: Cr, utils: Cu, classes: Cc} = Components;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-Cu.import("resource://testing-common/services/healthreport/utils.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-function run_test() {
-  do_get_profile();
-
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new AppInfoProvider();
-
-  run_next_test();
-});
-
-add_task(function test_collect_smoketest() {
-  let storage = yield Metrics.Storage("collect_smoketest");
-  let provider = new AppInfoProvider();
-  yield provider.init(storage);
-
-  let now = new Date();
-  yield provider.collectConstantData();
-
-  let m = provider.getMeasurement("appinfo", 2);
-  let data = yield storage.getMeasurementValues(m.id);
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let d = serializer.singular(data.singular);
-
-  do_check_eq(d._v, 2);
-  do_check_eq(d.vendor, "Mozilla");
-  do_check_eq(d.name, "xpcshell");
-  do_check_eq(d.id, "xpcshell@tests.mozilla.org");
-  do_check_eq(d.version, "1");
-  do_check_eq(d.appBuildID, "20121107");
-  do_check_eq(d.platformVersion, "p-ver");
-  do_check_eq(d.platformBuildID, "20121106");
-  do_check_eq(d.os, "XPCShell");
-  do_check_eq(d.xpcomabi, "noarch-spidermonkey");
-
-  do_check_eq(data.days.size, 1);
-  do_check_true(data.days.hasDay(now));
-  let day = data.days.getDay(now);
-  do_check_eq(day.size, 3);
-  do_check_true(day.has("isDefaultBrowser"));
-  do_check_true(day.has("isTelemetryEnabled"));
-  do_check_true(day.has("isBlocklistEnabled"));
-
-  // TODO Bug 827189 Actually test this properly. On some local builds, this
-  // is always -1 (the service throws). On buildbot, it seems to always be 0.
-  do_check_neq(day.get("isDefaultBrowser"), 1);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_record_version() {
-  let storage = yield Metrics.Storage("record_version");
-
-  let provider = new AppInfoProvider();
-  let now = new Date();
-  yield provider.init(storage);
-
-  // The provider records information on startup.
-  let m = provider.getMeasurement("versions", 2);
-  let data = yield m.getValues();
-
-  do_check_true(data.days.hasDay(now));
-  let day = data.days.getDay(now);
-  do_check_eq(day.size, 4);
-  do_check_true(day.has("appVersion"));
-  do_check_true(day.has("platformVersion"));
-  do_check_true(day.has("appBuildID"));
-  do_check_true(day.has("platformBuildID"));
-
-  let value = day.get("appVersion");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 1);
-  let ai = getAppInfo();
-  do_check_eq(value[0], ai.version);
-
-  value = day.get("platformVersion");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 1);
-  do_check_eq(value[0], ai.platformVersion);
-
-  value = day.get("appBuildID");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 1);
-  do_check_eq(value[0], ai.appBuildID);
-
-  value = day.get("platformBuildID");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 1);
-  do_check_eq(value[0], ai.platformBuildID);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_record_version_change() {
-  let storage = yield Metrics.Storage("record_version_change");
-
-  let provider = new AppInfoProvider();
-  let now = new Date();
-  yield provider.init(storage);
-  yield provider.shutdown();
-
-  let ai = getAppInfo();
-  ai.version = "new app version";
-  ai.platformVersion = "new platform version";
-  ai.appBuildID = "new app id";
-  ai.platformBuildID = "new platform id";
-  updateAppInfo(ai);
-
-  provider = new AppInfoProvider();
-  yield provider.init(storage);
-
-  // There should be 2 records in the versions history.
-  let m = provider.getMeasurement("versions", 2);
-  let data = yield m.getValues();
-  do_check_true(data.days.hasDay(now));
-  let day = data.days.getDay(now);
-
-  let value = day.get("appVersion");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 2);
-  do_check_eq(value[1], "new app version");
-
-  value = day.get("platformVersion");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 2);
-  do_check_eq(value[1], "new platform version");
-
-  // There should be 2 records in the buildID history.
-  value = day.get("appBuildID");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 2);
-  do_check_eq(value[1], "new app id");
-
-  value = day.get("platformBuildID");
-  do_check_true(Array.isArray(value));
-  do_check_eq(value.length, 2);
-  do_check_eq(value[1], "new platform id");
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_record_telemetry() {
-  let storage = yield Metrics.Storage("record_telemetry");
-  let provider;
-
-  let now = new Date();
-
-  Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
-  provider = new AppInfoProvider();
-  yield provider.init(storage);
-  yield provider.collectConstantData();
-
-  let m = provider.getMeasurement("appinfo", 2);
-  let data = yield m.getValues();
-  let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(1, d.isTelemetryEnabled);
-  yield provider.shutdown();
-
-  Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
-  provider = new AppInfoProvider();
-  yield provider.init(storage);
-  yield provider.collectConstantData();
-
-  m = provider.getMeasurement("appinfo", 2);
-  data = yield m.getValues();
-  d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(0, d.isTelemetryEnabled);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-add_task(function test_record_blocklist() {
-  let storage = yield Metrics.Storage("record_blocklist");
-
-  let now = new Date();
-
-  Services.prefs.setBoolPref("extensions.blocklist.enabled", true);
-  let provider = new AppInfoProvider();
-  yield provider.init(storage);
-  yield provider.collectConstantData();
-
-  let m = provider.getMeasurement("appinfo", 2);
-  let data = yield m.getValues();
-  let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(d.isBlocklistEnabled, 1);
-  yield provider.shutdown();
-
-  Services.prefs.setBoolPref("extensions.blocklist.enabled", false);
-  provider = new AppInfoProvider();
-  yield provider.init(storage);
-  yield provider.collectConstantData();
-
-  m = provider.getMeasurement("appinfo", 2);
-  data = yield m.getValues();
-  d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(d.isBlocklistEnabled, 0);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-add_task(function test_record_app_update () {
-  let storage = yield Metrics.Storage("record_update");
-
-  Services.prefs.setBoolPref("app.update.enabled", true);
-  Services.prefs.setBoolPref("app.update.auto", true);
-  let provider = new AppInfoProvider();
-  yield provider.init(storage);
-  let now = new Date();
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("update", 1);
-  let data = yield m.getValues();
-  let d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(d.enabled, 1);
-  do_check_eq(d.autoDownload, 1);
-
-  Services.prefs.setBoolPref("app.update.enabled", false);
-  Services.prefs.setBoolPref("app.update.auto", false);
-
-  yield provider.collectDailyData();
-  data = yield m.getValues();
-  d = yield m.serializer(m.SERIALIZE_JSON).daily(data.days.getDay(now));
-  do_check_eq(d.enabled, 0);
-  do_check_eq(d.autoDownload, 0);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_healthreporter_integration () {
-  let reporter = getHealthReporter("healthreporter_integration");
-  yield reporter.init();
-
-  try {
-    yield reporter._providerManager.registerProviderFromType(AppInfoProvider);
-    yield reporter.collectMeasurements();
-
-    let payload = yield reporter.getJSONPayload(true);
-    let days = payload['data']['days'];
-
-    for (let [day, measurements] in Iterator(days)) {
-      do_check_eq(Object.keys(measurements).length, 3);
-      do_check_true("org.mozilla.appInfo.appinfo" in measurements);
-      do_check_true("org.mozilla.appInfo.update" in measurements);
-      do_check_true("org.mozilla.appInfo.versions" in measurements);
-    }
-  } finally {
-    yield reporter._shutdown();
-  }
-});
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_crashes.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-Cu.import("resource://testing-common/AppData.jsm");
-Cu.import("resource://testing-common/services/healthreport/utils.jsm");
-Cu.import("resource://testing-common/CrashManagerTest.jsm");
-
-
-function run_test() {
-  run_next_test();
-}
-
-add_task(function* init() {
-  do_get_profile();
-  yield makeFakeAppDir();
-});
-
-add_task(function test_constructor() {
-  let provider = new CrashesProvider();
-});
-
-add_task(function* test_init() {
-  let storage = yield Metrics.Storage("init");
-  let provider = new CrashesProvider();
-  yield provider.init(storage);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-add_task(function* test_collect() {
-  let storage = yield Metrics.Storage("collect");
-  let provider = new CrashesProvider();
-  yield provider.init(storage);
-
-  // Install custom manager so we don't interfere with other tests.
-  let manager = yield getManager();
-  provider._manager = manager;
-
-  let day1 = new Date(2014, 0, 1, 0, 0, 0);
-  let day2 = new Date(2014, 0, 3, 0, 0, 0);
-
-  yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
-                         manager.CRASH_TYPE_CRASH,
-                         "mc1", day1, { OOMAllocationSize: 1073741824 });
-  yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
-                         manager.CRASH_TYPE_CRASH,
-                         "mc2", day1);
-  yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
-                         manager.CRASH_TYPE_HANG,
-                         "ch", day1);
-  yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
-                         manager.CRASH_TYPE_CRASH,
-                         "pc", day1);
-
-  yield manager.addSubmissionAttempt("mc1", "sub1", day1);
-  yield manager.addSubmissionResult("mc1", "sub1", day1,
-                                    manager.SUBMISSION_RESULT_OK);
-  yield manager.addSubmissionAttempt("ch", "sub1", day1);
-  yield manager.addSubmissionResult("ch", "sub1", day1,
-                                    manager.SUBMISSION_RESULT_FAILED);
-  yield manager.addSubmissionAttempt("ch", "sub2", day1);
-  yield manager.addSubmissionResult("ch", "sub2", day1,
-                                    manager.SUBMISSION_RESULT_FAILED);
-  yield manager.addSubmissionAttempt("ch", "sub3", day1);
-  yield manager.addSubmissionResult("ch", "sub3", day1,
-                                    manager.SUBMISSION_RESULT_OK);
-
-  yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
-                         manager.CRASH_TYPE_HANG,
-                         "mh", day2);
-  yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
-                         manager.CRASH_TYPE_CRASH,
-                         "cc", day2);
-  yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
-                         manager.CRASH_TYPE_HANG,
-                         "ph", day2);
-  yield manager.addCrash(manager.PROCESS_TYPE_GMPLUGIN,
-                         manager.CRASH_TYPE_CRASH,
-                         "gmpc", day2);
-
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("crashes", 6);
-  let values = yield m.getValues();
-  do_check_eq(values.days.size, 2);
-  do_check_true(values.days.hasDay(day1));
-  do_check_true(values.days.hasDay(day2));
-
-  let value = values.days.getDay(day1);
-  do_check_true(value.has("main-crash"));
-  do_check_eq(value.get("main-crash"), 2);
-  do_check_true(value.has("main-crash-oom"));
-  do_check_eq(value.get("main-crash-oom"), 1);
-  do_check_true(value.has("content-hang"));
-  do_check_eq(value.get("content-hang"), 1);
-  do_check_true(value.has("plugin-crash"));
-  do_check_eq(value.get("plugin-crash"), 1);
-
-  do_check_true(value.has("main-crash-submission-succeeded"));
-  do_check_eq(value.get("main-crash-submission-succeeded"), 1);
-  do_check_true(value.has("content-hang-submission-failed"));
-  do_check_eq(value.get("content-hang-submission-failed"), 2);
-  do_check_true(value.has("content-hang-submission-succeeded"));
-  do_check_eq(value.get("content-hang-submission-succeeded"), 1);
-
-  value = values.days.getDay(day2);
-  do_check_true(value.has("main-hang"));
-  do_check_eq(value.get("main-hang"), 1);
-  do_check_true(value.has("content-crash"));
-  do_check_eq(value.get("content-crash"), 1);
-  do_check_true(value.has("plugin-hang"));
-  do_check_eq(value.get("plugin-hang"), 1);
-  do_check_true(value.has("gmplugin-crash"));
-  do_check_eq(value.get("gmplugin-crash"), 1);
-
-  // Check that adding a new crash increments counter on next collect.
-  yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
-                         manager.CRASH_TYPE_HANG,
-                         "mc3", day2);
-
-  yield provider.collectDailyData();
-  values = yield m.getValues();
-  value = values.days.getDay(day2);
-  do_check_eq(value.get("main-hang"), 2);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_hotfix.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-const EXAMPLE_2014052701 = {
-  "upgradedFrom":"13.0.1",
-  "uninstallReason":"SUCCESSFUL_UPGRADE",
-  "_entityID":null,
-  "forensicsID":"29525548-b653-49db-bfb8-a160cdfbeb4a",
-  "_installInProgress":false,
-  "_everCompatible":true,
-  "reportedWindowsVersion":[6,1,1],
-  "actualWindowsVersion":[6,1,1],
-  "firstNotifyDay":0,
-  "lastNotifyDay":0,
-  "downloadAttempts":1,
-  "downloadFailures":0,
-  "installAttempts":1,
-  "installSuccesses":1,
-  "installLauncherFailures":0,
-  "installFailures":0,
-  "notificationsShown":0,
-  "notificationsClicked":0,
-  "notificationsDismissed":0,
-  "notificationsRemoved":0,
-  "launcherExitCodes":{"0":1}
-};
-
-function run_test() {
-  run_next_test();
-}
-
-add_task(function* init() {
-  do_get_profile();
-});
-
-add_task(function test_constructor() {
-  new HotfixProvider();
-});
-
-add_task(function* test_init() {
-  let storage = yield Metrics.Storage("init");
-  let provider = new HotfixProvider();
-  yield provider.init(storage);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_empty() {
-  let storage = yield Metrics.Storage("collect_empty");
-  let provider = new HotfixProvider();
-  yield provider.init(storage);
-
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("update", 1);
-  let data = yield m.getValues();
-  Assert.equal(data.singular.size, 0);
-  Assert.equal(data.days.size, 0);
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_20140527() {
-  let storage = yield Metrics.Storage("collect_20140527");
-  let provider = new HotfixProvider();
-  yield provider.init(storage);
-
-  let path = OS.Path.join(OS.Constants.Path.profileDir,
-                          "hotfix.v20140527.01.json");
-  let encoder = new TextEncoder();
-  yield OS.File.writeAtomic(path,
-                            encoder.encode(JSON.stringify(EXAMPLE_2014052701)));
-
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("update", 1);
-  let data = yield m.getValues();
-  let s = data.singular;
-  Assert.equal(s.size, 7);
-  Assert.equal(s.get("v20140527.upgradedFrom")[1], "13.0.1");
-  Assert.equal(s.get("v20140527.uninstallReason")[1], "SUCCESSFUL_UPGRADE");
-  Assert.equal(s.get("v20140527.downloadAttempts")[1], 1);
-  Assert.equal(s.get("v20140527.downloadFailures")[1], 0);
-  Assert.equal(s.get("v20140527.installAttempts")[1], 1);
-  Assert.equal(s.get("v20140527.installFailures")[1], 0);
-  Assert.equal(s.get("v20140527.notificationsShown")[1], 0);
-
-  // Ensure the dynamic fields get serialized.
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let d = serializer.singular(s);
-
-  Assert.deepEqual(d, {
-    "_v": 1,
-    "v20140527.upgradedFrom": "13.0.1",
-    "v20140527.uninstallReason": "SUCCESSFUL_UPGRADE",
-    "v20140527.downloadAttempts": 1,
-    "v20140527.downloadFailures": 0,
-    "v20140527.installAttempts": 1,
-    "v20140527.installFailures": 0,
-    "v20140527.notificationsShown": 0,
-  });
-
-  // Don't interfere with next test.
-  yield OS.File.remove(path);
-
-  yield storage.close();
-});
-
-add_task(function* test_collect_multiple_versions() {
-  let storage = yield Metrics.Storage("collect_multiple_versions");
-  let provider = new HotfixProvider();
-  yield provider.init(storage);
-
-  let p1 = {
-    upgradedFrom: "12.0",
-    uninstallReason: "SUCCESSFUL_UPGRADE",
-    downloadAttempts: 3,
-    downloadFailures: 1,
-    installAttempts: 1,
-    installFailures: 1,
-    notificationsShown: 2,
-  };
-
-  let p2 = {
-    downloadAttempts: 5,
-    downloadFailures: 3,
-    installAttempts: 2,
-    installFailures: 2,
-    uninstallReason: null,
-    notificationsShown: 1,
-  };
-
-  let path1 = OS.Path.join(OS.Constants.Path.profileDir, "updateHotfix.v20140601.json");
-  let path2 = OS.Path.join(OS.Constants.Path.profileDir, "updateHotfix.v20140701.json");
-
-  let encoder = new TextEncoder();
-  yield OS.File.writeAtomic(path1, encoder.encode(JSON.stringify(p1)));
-  yield OS.File.writeAtomic(path2, encoder.encode(JSON.stringify(p2)));
-
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("update", 1);
-  let data = yield m.getValues();
-
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let d = serializer.singular(data.singular);
-
-  Assert.deepEqual(d, {
-    "_v": 1,
-    "v20140601.upgradedFrom": "12.0",
-    "v20140601.uninstallReason": "SUCCESSFUL_UPGRADE",
-    "v20140601.downloadAttempts": 3,
-    "v20140601.downloadFailures": 1,
-    "v20140601.installAttempts": 1,
-    "v20140601.installFailures": 1,
-    "v20140601.notificationsShown": 2,
-    "v20140701.uninstallReason": "STILL_INSTALLED",
-    "v20140701.downloadAttempts": 5,
-    "v20140701.downloadFailures": 3,
-    "v20140701.installAttempts": 2,
-    "v20140701.installFailures": 2,
-    "v20140701.notificationsShown": 1,
-  });
-
-  // Don't interfere with next test.
-  yield OS.File.remove(path1);
-  yield OS.File.remove(path2);
-
-  yield storage.close();
-});
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_places.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-
-function run_test() {
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new PlacesProvider();
-
-  run_next_test();
-});
-
-add_task(function test_collect_smoketest() {
-  let storage = yield Metrics.Storage("collect_smoketest");
-  let provider = new PlacesProvider();
-
-  yield provider.init(storage);
-
-  let now = new Date();
-  yield provider.collectDailyData();
-
-  let m = provider.getMeasurement("places", 1);
-  let data = yield storage.getMeasurementValues(m.id);
-  do_check_eq(data.days.size, 1);
-  do_check_true(data.days.hasDay(now));
-
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let day = serializer.daily(data.days.getDay(now));
-
-  do_check_eq(day._v, 1);
-  do_check_eq(Object.keys(day).length, 3);
-  do_check_eq(day.pages, 0);
-  do_check_eq(day.bookmarks, 0);
-
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_searches.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-var bsp = Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-const DEFAULT_ENGINES = [
-  {name: "Amazon.com",    identifier: "amazondotcom"},
-  {name: "Bing",          identifier: "bing"},
-  {name: "Google",        identifier: "google"},
-  {name: "Yahoo",         identifier: "yahoo"},
-  {name: "Foobar Search", identifier: "foobar"},
-];
-
-function MockSearchCountMeasurement() {
-  bsp.SearchCountMeasurement3.call(this);
-}
-MockSearchCountMeasurement.prototype = {
-  __proto__: bsp.SearchCountMeasurement3.prototype,
-};
-
-function MockSearchesProvider() {
-  SearchesProvider.call(this);
-}
-MockSearchesProvider.prototype = {
-  __proto__: SearchesProvider.prototype,
-  measurementTypes: [MockSearchCountMeasurement],
-};
-
-function run_test() {
-  // Tell the search service we are running in the US.  This also has the
-  // desired side-effect of preventing our geoip lookup.
-  Services.prefs.setBoolPref("browser.search.isUS", true);
-  Services.prefs.setCharPref("browser.search.countryCode", "US");
-
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new SearchesProvider();
-
-  run_next_test();
-});
-
-add_task(function* test_record() {
-  let storage = yield Metrics.Storage("record");
-  let provider = new MockSearchesProvider();
-
-  yield provider.init(storage);
-
-  let now = new Date();
-
-  // Record searches for all but one of our defaults, and one engine that's
-  // not a default.
-  for (let engine of DEFAULT_ENGINES.concat([{name: "Not Default", identifier: "notdef"}])) {
-    if (engine.identifier == "yahoo") {
-      continue;
-    }
-    yield provider.recordSearch(engine, "abouthome");
-    yield provider.recordSearch(engine, "contextmenu");
-    yield provider.recordSearch(engine, "newtab");
-    yield provider.recordSearch(engine, "searchbar");
-    yield provider.recordSearch(engine, "urlbar");
-  }
-
-  // Invalid sources should throw.
-  let errored = false;
-  try {
-    yield provider.recordSearch(DEFAULT_ENGINES[0], "bad source");
-  } catch (ex) {
-    errored = true;
-  } finally {
-    do_check_true(errored);
-  }
-
-  let m = provider.getMeasurement("counts", 3);
-  let data = yield m.getValues();
-  do_check_eq(data.days.size, 1);
-  do_check_true(data.days.hasDay(now));
-
-  let day = data.days.getDay(now);
-  for (let engine of DEFAULT_ENGINES) {
-    let identifier = engine.identifier;
-    let expected = identifier != "yahoo";
-
-    for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
-      let field = identifier + "." + source;
-      if (expected) {
-        do_check_true(day.has(field));
-        do_check_eq(day.get(field), 1);
-      } else {
-        do_check_false(day.has(field));
-      }
-    }
-  }
-
-  // Also, check that our non-default engine contributed, with a computed
-  // identifier.
-  let identifier = "notdef";
-  for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
-    let field = identifier + "." + source;
-    do_check_true(day.has(field));
-  }
-
-  yield storage.close();
-});
-
-add_task(function* test_includes_other_fields() {
-  let storage = yield Metrics.Storage("includes_other_fields");
-  let provider = new MockSearchesProvider();
-
-  yield provider.init(storage);
-  let m = provider.getMeasurement("counts", 3);
-
-  // Register a search against a provider that isn't live in this session.
-  let id = yield m.storage.registerField(m.id, "test.searchbar",
-                                         Metrics.Storage.FIELD_DAILY_COUNTER);
-
-  let testField = "test.searchbar";
-  let now = new Date();
-  yield m.storage.incrementDailyCounterFromFieldID(id, now);
-
-  // Make sure we don't know about it.
-  do_check_false(testField in m.fields);
-
-  // But we want to include it in payloads.
-  do_check_true(m.shouldIncludeField(testField));
-
-  // And we do so.
-  let data = yield provider.storage.getMeasurementValues(m.id);
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let formatted = serializer.daily(data.days.getDay(now));
-  do_check_true(testField in formatted);
-  do_check_eq(formatted[testField], 1);
-
-  yield storage.close();
-});
-
-add_task(function* test_default_search_engine() {
-  let storage = yield Metrics.Storage("default_search_engine");
-  let provider = new SearchesProvider();
-  yield provider.init(storage);
-
-  let m = provider.getMeasurement("engines", 2);
-
-  let now = new Date();
-  yield provider.collectDailyData();
-  let data = yield m.getValues();
-  Assert.ok(data.days.hasDay(now));
-
-  let day = data.days.getDay(now);
-  Assert.equal(day.size, 1);
-  Assert.ok(day.has("default"));
-
-  // test environment doesn't have a default engine.
-  Assert.equal(day.get("default"), "NONE");
-
-  Services.search.addEngineWithDetails("testdefault",
-                                       "http://localhost/icon.png",
-                                       null,
-                                       "test description",
-                                       "GET",
-                                       "http://localhost/search/%s");
-  let engine1 = Services.search.getEngineByName("testdefault");
-  Assert.ok(engine1);
-  Services.search.defaultEngine = engine1;
-
-  yield provider.collectDailyData();
-  data = yield m.getValues();
-  Assert.equal(data.days.getDay(now).get("default"), "other-testdefault");
-
-  // If no cohort identifier is set, we shouldn't report a cohort.
-  Assert.equal(data.days.getDay(now).get("cohort"), undefined);
-
-  // Set a cohort identifier and verify we record it.
-  Services.prefs.setCharPref("browser.search.cohort", "testcohort");
-  yield provider.collectDailyData();
-  data = yield m.getValues();
-  Assert.equal(data.days.getDay(now).get("cohort"), "testcohort");
-
-  yield storage.close();
-});
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_sessions.js
+++ /dev/null
@@ -1,217 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {utils: Cu} = Components;
-
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/services-common/utils.js");
-Cu.import("resource://gre/modules/SessionRecorder.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-
-function run_test() {
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new SessionsProvider();
-
-  run_next_test();
-});
-
-add_task(function test_init() {
-  let storage = yield Metrics.Storage("init");
-  let provider = new SessionsProvider();
-  yield provider.init(storage);
-  yield provider.shutdown();
-
-  yield storage.close();
-});
-
-function monkeypatchStartupInfo(recorder, start=new Date(), offset=500) {
-  Object.defineProperty(recorder, "_getStartupInfo", {
-    value: function _getStartupInfo() {
-      return {
-        process: start,
-        main: new Date(start.getTime() + offset),
-        firstPaint: new Date(start.getTime() + 2 * offset),
-        sessionRestored: new Date(start.getTime() + 3 * offset),
-      };
-    }
-  });
-}
-
-function sleep(wait) {
-  let deferred = Promise.defer();
-
-  let timer = CommonUtils.namedTimer(function onTimer() {
-    deferred.resolve();
-  }, wait, deferred.promise, "_sleepTimer");
-
-  return deferred.promise;
-}
-
-function getProvider(name, now=new Date(), init=true) {
-  return Task.spawn(function () {
-    let storage = yield Metrics.Storage(name);
-    let provider = new SessionsProvider();
-
-    let recorder = new SessionRecorder("testing." + name + ".sessions.");
-    monkeypatchStartupInfo(recorder, now);
-    provider.healthReporter = {sessionRecorder: recorder};
-    recorder.onStartup();
-
-    if (init) {
-      yield provider.init(storage);
-    }
-
-    throw new Task.Result([provider, storage, recorder]);
-  });
-}
-
-add_task(function test_current_session() {
-  let now = new Date();
-  let [provider, storage, recorder] = yield getProvider("current_session", now);
-
-  yield sleep(25);
-  recorder.onActivity(true);
-
-  let current = provider.getMeasurement("current", 3);
-  let values = yield current.getValues();
-  let fields = values.singular;
-
-  for (let field of ["startDay", "activeTicks", "totalTime", "main", "firstPaint", "sessionRestored"]) {
-    do_check_true(fields.has(field));
-  }
-
-  do_check_eq(fields.get("startDay")[1], Metrics.dateToDays(now));
-  do_check_eq(fields.get("totalTime")[1], recorder.totalTime);
-  do_check_eq(fields.get("activeTicks")[1], 1);
-  do_check_eq(fields.get("main")[1], 500);
-  do_check_eq(fields.get("firstPaint")[1], 1000);
-  do_check_eq(fields.get("sessionRestored")[1], 1500);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_collect() {
-  let now = new Date();
-  let [provider, storage, recorder] = yield getProvider("collect");
-
-  recorder.onShutdown();
-  yield sleep(25);
-
-  for (let i = 0; i < 5; i++) {
-    let recorder2 = new SessionRecorder("testing.collect.sessions.");
-    recorder2.onStartup();
-    yield sleep(25);
-    recorder2.onShutdown();
-    yield sleep(25);
-  }
-
-  recorder = new SessionRecorder("testing.collect.sessions.");
-  recorder.onStartup();
-
-  // Collecting the provider should prune all previous sessions.
-  let sessions = recorder.getPreviousSessions();
-  do_check_eq(Object.keys(sessions).length, 6);
-  yield provider.collectConstantData();
-  sessions = recorder.getPreviousSessions();
-  do_check_eq(Object.keys(sessions).length, 0);
-
-  // And those previous sessions should make it to storage.
-  let daily = provider.getMeasurement("previous", 3);
-  let values = yield daily.getValues();
-  do_check_true(values.days.hasDay(now));
-  do_check_eq(values.days.size, 1);
-  let day = values.days.getDay(now);
-  do_check_eq(day.size, 5);
-  let previousStorageCount = day.get("main").length;
-
-  for (let field of ["cleanActiveTicks", "cleanTotalTime", "main", "firstPaint", "sessionRestored"]) {
-    do_check_true(day.has(field));
-    do_check_true(Array.isArray(day.get(field)));
-    do_check_eq(day.get(field).length, 6);
-  }
-
-  let lastIndex = yield provider.getState("lastSession");
-  do_check_eq(lastIndex, "" + (previousStorageCount - 1)); // 0-indexed
-
-  // Fake an aborted session. If we create a 2nd recorder against the same
-  // prefs branch as a running one, this simulates what would happen if the
-  // first recorder didn't shut down.
-  let recorder2 = new SessionRecorder("testing.collect.sessions.");
-  recorder2.onStartup();
-  do_check_eq(Object.keys(recorder.getPreviousSessions()).length, 1);
-  yield provider.collectConstantData();
-  do_check_eq(Object.keys(recorder.getPreviousSessions()).length, 0);
-
-  values = yield daily.getValues();
-  day = values.days.getDay(now);
-  do_check_eq(day.size, previousStorageCount + 1);
-  previousStorageCount = day.get("main").length;
-  for (let field of ["abortedActiveTicks", "abortedTotalTime"]) {
-    do_check_true(day.has(field));
-    do_check_true(Array.isArray(day.get(field)));
-    do_check_eq(day.get(field).length, 1);
-  }
-
-  lastIndex = yield provider.getState("lastSession");
-  do_check_eq(lastIndex, "" + (previousStorageCount - 1));
-
-  recorder.onShutdown();
-  recorder2.onShutdown();
-
-  // If we try to insert a already-inserted session, it will be ignored.
-  recorder = new SessionRecorder("testing.collect.sessions.");
-  recorder._currentIndex = recorder._currentIndex - 1;
-  recorder._prunedIndex = recorder._currentIndex;
-  recorder.onStartup();
-  // Session is left over from recorder2.
-  sessions = recorder.getPreviousSessions();
-  do_check_eq(Object.keys(sessions).length, 1);
-  do_check_true(previousStorageCount - 1 in sessions);
-  yield provider.collectConstantData();
-  lastIndex = yield provider.getState("lastSession");
-  do_check_eq(lastIndex, "" + (previousStorageCount - 1));
-  values = yield daily.getValues();
-  day = values.days.getDay(now);
-  // We should not get additional entry.
-  do_check_eq(day.get("main").length, previousStorageCount);
-  recorder.onShutdown();
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
-add_task(function test_serialization() {
-  let [provider, storage, recorder] = yield getProvider("serialization");
-
-  yield sleep(1025);
-  recorder.onActivity(true);
-
-  let current = provider.getMeasurement("current", 3);
-  let data = yield current.getValues();
-  do_check_true("singular" in data);
-
-  let serializer = current.serializer(current.SERIALIZE_JSON);
-  let fields = serializer.singular(data.singular);
-
-  do_check_eq(fields._v, 3);
-  do_check_eq(fields.activeTicks, 1);
-  do_check_eq(fields.startDay, Metrics.dateToDays(recorder.startDate));
-  do_check_eq(fields.main, 500);
-  do_check_eq(fields.firstPaint, 1000);
-  do_check_eq(fields.sessionRestored, 1500);
-  do_check_true(fields.totalTime > 0);
-
-  yield provider.shutdown();
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/test_provider_sysinfo.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var {interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Metrics.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
-
-
-function run_test() {
-  run_next_test();
-}
-
-add_test(function test_constructor() {
-  let provider = new SysInfoProvider();
-
-  run_next_test();
-});
-
-add_task(function test_collect_smoketest() {
-  let storage = yield Metrics.Storage("collect_smoketest");
-  let provider = new SysInfoProvider();
-  yield provider.init(storage);
-
-  yield provider.collectConstantData();
-
-  let m = provider.getMeasurement("sysinfo", 2);
-  let data = yield storage.getMeasurementValues(m.id);
-  let serializer = m.serializer(m.SERIALIZE_JSON);
-  let d = serializer.singular(data.singular);
-
-  do_check_eq(d._v, 2);
-  do_check_true(d.cpuCount > 0);
-  do_check_neq(d.name, null);
-
-  yield storage.close();
-});
-
deleted file mode 100644
--- a/services/healthreport/tests/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,19 +0,0 @@
-[DEFAULT]
-head = head.js
-tail =
-skip-if = toolkit == 'android' || toolkit == 'gonk'
-
-[test_load_modules.js]
-[test_profile.js]
-[test_provider_addons.js]
-skip-if = buildapp == 'mulet'
-tags = addons
-[test_provider_appinfo.js]
-[test_provider_crashes.js]
-skip-if = !crashreporter
-[test_provider_hotfix.js]
-[test_provider_places.js]
-[test_provider_searches.js]
-[test_provider_sysinfo.js]
-[test_provider_sessions.js]
-
--- a/services/moz.build
+++ b/services/moz.build
@@ -7,22 +7,16 @@
 DIRS += [
     'common',
     'crypto',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' or CONFIG['MOZ_B2GDROID']:
     DIRS += ['fxaccounts']
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
-    # MOZ_SERVICES_HEALTHREPORT and therefore MOZ_DATA_REPORTING are
-    # defined on Android, but these features are implemented using Java.
-    if CONFIG['MOZ_SERVICES_HEALTHREPORT']:
-        DIRS += ['healthreport']
-
 if CONFIG['MOZ_SERVICES_METRICS']:
     DIRS += ['metrics']
 
 if CONFIG['MOZ_SERVICES_SYNC']:
     DIRS += ['sync']
 
 if CONFIG['MOZ_B2G'] or CONFIG['MOZ_B2GDROID']:
     DIRS += ['mobileid']
rename from services/healthreport/docs/architecture.rst
rename to toolkit/components/telemetry/docs/fhr/architecture.rst
rename from services/healthreport/docs/dataformat.rst
rename to toolkit/components/telemetry/docs/fhr/dataformat.rst
rename from services/healthreport/docs/identifiers.rst
rename to toolkit/components/telemetry/docs/fhr/identifiers.rst
rename from services/healthreport/docs/index.rst
rename to toolkit/components/telemetry/docs/fhr/index.rst
--- a/services/healthreport/docs/index.rst
+++ b/toolkit/components/telemetry/docs/fhr/index.rst
@@ -1,16 +1,16 @@
 .. _healthreport:
 
-=====================
-Firefox Health Report
-=====================
+================================
+Firefox Health Report (Obsolete)
+================================
 
-``/services/healthreport`` contains the implementation of the
-``Firefox Health Report`` (FHR).
+**Firefox Health Report (FHR) is obsolete and no longer ships with Firefox.
+This documentation will live here for a few more cycles.**
 
 Firefox Health Report is a background service that collects application
 metrics and periodically submits them to a central server. The core
 parts of the service are implemented in this directory. However, the
 actual XPCOM service is implemented in the
 :ref:`data_reporting_service`.
 
 The core types can actually be instantiated multiple times and used to
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -76,8 +76,9 @@ enums.inputs = histogram_files
 DEFINES['MOZ_APP_VERSION'] = '"%s"' % CONFIG['MOZ_APP_VERSION']
 
 LOCAL_INCLUDES += [
     '/xpcom/build',
     '/xpcom/threads',
 ]
 
 SPHINX_TREES['telemetry'] = 'docs'
+SPHINX_TREES['healthreport'] = 'docs/fhr'