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 316181 096d46bdaf86112bc1bf350fec18d5d4063ab0c2
parent 316180 96e0326e798579fda3e4babf0a253b43e19e2635
child 316182 3124025d114712f6717e6cf6a4ba930f0ab02ac3
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche
bugs1234526
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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'