services/datareporting/DataReportingService.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/datareporting/DataReportingService.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,297 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
    1.11 +
    1.12 +Cu.import("resource://gre/modules/Preferences.jsm");
    1.13 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.14 +Cu.import("resource://services-common/utils.js");
    1.15 +
    1.16 +
    1.17 +const ROOT_BRANCH = "datareporting.";
    1.18 +const POLICY_BRANCH = ROOT_BRANCH + "policy.";
    1.19 +const SESSIONS_BRANCH = ROOT_BRANCH + "sessions.";
    1.20 +const HEALTHREPORT_BRANCH = ROOT_BRANCH + "healthreport.";
    1.21 +const HEALTHREPORT_LOGGING_BRANCH = HEALTHREPORT_BRANCH + "logging.";
    1.22 +const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
    1.23 +const DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC = 60 * 1000;
    1.24 +
    1.25 +/**
    1.26 + * The Firefox Health Report XPCOM service.
    1.27 + *
    1.28 + * External consumers will be interested in the "reporter" property of this
    1.29 + * service. This property is a `HealthReporter` instance that powers the
    1.30 + * service. The property may be null if the Health Report service is not
    1.31 + * enabled.
    1.32 + *
    1.33 + * EXAMPLE USAGE
    1.34 + * =============
    1.35 + *
    1.36 + * let reporter = Cc["@mozilla.org/datareporting/service;1"]
    1.37 + *                  .getService(Ci.nsISupports)
    1.38 + *                  .wrappedJSObject
    1.39 + *                  .healthReporter;
    1.40 + *
    1.41 + * if (reporter.haveRemoteData) {
    1.42 + *   // ...
    1.43 + * }
    1.44 + *
    1.45 + * IMPLEMENTATION NOTES
    1.46 + * ====================
    1.47 + *
    1.48 + * In order to not adversely impact application start time, the `HealthReporter`
    1.49 + * instance is not initialized until a few seconds after "final-ui-startup."
    1.50 + * The exact delay is configurable via preferences so it can be adjusted with
    1.51 + * a hotfix extension if the default value is ever problematic. Because of the
    1.52 + * overhead with the initial creation of the database, the first run is delayed
    1.53 + * even more than subsequent runs. This does mean that the first moments of
    1.54 + * browser activity may be lost by FHR.
    1.55 + *
    1.56 + * Shutdown of the `HealthReporter` instance is handled completely within the
    1.57 + * instance (it registers observers on initialization). See the notes on that
    1.58 + * type for more.
    1.59 + */
    1.60 +this.DataReportingService = function () {
    1.61 +  this.wrappedJSObject = this;
    1.62 +
    1.63 +  this._quitting = false;
    1.64 +
    1.65 +  this._os = Cc["@mozilla.org/observer-service;1"]
    1.66 +               .getService(Ci.nsIObserverService);
    1.67 +}
    1.68 +
    1.69 +DataReportingService.prototype = Object.freeze({
    1.70 +  classID: Components.ID("{41f6ae36-a79f-4613-9ac3-915e70f83789}"),
    1.71 +
    1.72 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
    1.73 +                                         Ci.nsISupportsWeakReference]),
    1.74 +
    1.75 +  //---------------------------------------------
    1.76 +  // Start of policy listeners.
    1.77 +  //---------------------------------------------
    1.78 +
    1.79 +  /**
    1.80 +   * Called when policy requests data upload.
    1.81 +   */
    1.82 +  onRequestDataUpload: function (request) {
    1.83 +    if (!this.healthReporter) {
    1.84 +      return;
    1.85 +    }
    1.86 +
    1.87 +    this.healthReporter.requestDataUpload(request);
    1.88 +  },
    1.89 +
    1.90 +  onNotifyDataPolicy: function (request) {
    1.91 +    Observers.notify("datareporting:notify-data-policy:request", request);
    1.92 +  },
    1.93 +
    1.94 +  onRequestRemoteDelete: function (request) {
    1.95 +    if (!this.healthReporter) {
    1.96 +      return;
    1.97 +    }
    1.98 +
    1.99 +    this.healthReporter.deleteRemoteData(request);
   1.100 +  },
   1.101 +
   1.102 +  //---------------------------------------------
   1.103 +  // End of policy listeners.
   1.104 +  //---------------------------------------------
   1.105 +
   1.106 +  observe: function observe(subject, topic, data) {
   1.107 +    switch (topic) {
   1.108 +      case "app-startup":
   1.109 +        this._os.addObserver(this, "profile-after-change", true);
   1.110 +        break;
   1.111 +
   1.112 +      case "profile-after-change":
   1.113 +        this._os.removeObserver(this, "profile-after-change");
   1.114 +
   1.115 +        try {
   1.116 +          this._prefs = new Preferences(HEALTHREPORT_BRANCH);
   1.117 +
   1.118 +          // We don't initialize the sessions recorder unless Health Report is
   1.119 +          // around to provide pruning of data.
   1.120 +          //
   1.121 +          // FUTURE consider having the SessionsRecorder always enabled and/or
   1.122 +          // living in its own XPCOM service.
   1.123 +          if (this._prefs.get("service.enabled", true)) {
   1.124 +            this.sessionRecorder = new SessionRecorder(SESSIONS_BRANCH);
   1.125 +            this.sessionRecorder.onStartup();
   1.126 +          }
   1.127 +
   1.128 +          // We can't interact with prefs until after the profile is present.
   1.129 +          let policyPrefs = new Preferences(POLICY_BRANCH);
   1.130 +          this.policy = new DataReportingPolicy(policyPrefs, this._prefs, this);
   1.131 +
   1.132 +          this._os.addObserver(this, "sessionstore-windows-restored", true);
   1.133 +        } catch (ex) {
   1.134 +          Cu.reportError("Exception when initializing data reporting service: " +
   1.135 +                         CommonUtils.exceptionStr(ex));
   1.136 +        }
   1.137 +        break;
   1.138 +
   1.139 +      case "sessionstore-windows-restored":
   1.140 +        this._os.removeObserver(this, "sessionstore-windows-restored");
   1.141 +        this._os.addObserver(this, "quit-application", false);
   1.142 +
   1.143 +        this.policy.startPolling();
   1.144 +
   1.145 +        // Don't initialize Firefox Health Reporter collection and submission
   1.146 +        // service unless it is enabled.
   1.147 +        if (!this._prefs.get("service.enabled", true)) {
   1.148 +          return;
   1.149 +        }
   1.150 +
   1.151 +        let haveFirstRun = this._prefs.get("service.firstRun", false);
   1.152 +        let delayInterval;
   1.153 +
   1.154 +        if (haveFirstRun) {
   1.155 +          delayInterval = this._prefs.get("service.loadDelayMsec") ||
   1.156 +                          DEFAULT_LOAD_DELAY_MSEC;
   1.157 +        } else {
   1.158 +          delayInterval = this._prefs.get("service.loadDelayFirstRunMsec") ||
   1.159 +                          DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC;
   1.160 +        }
   1.161 +
   1.162 +        // Delay service loading a little more so things have an opportunity
   1.163 +        // to cool down first.
   1.164 +        this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.165 +        this.timer.initWithCallback({
   1.166 +          notify: function notify() {
   1.167 +            delete this.timer;
   1.168 +
   1.169 +            // There could be a race between "quit-application" firing and
   1.170 +            // this callback being invoked. We close that door.
   1.171 +            if (this._quitting) {
   1.172 +              return;
   1.173 +            }
   1.174 +
   1.175 +            // Side effect: instantiates the reporter instance if not already
   1.176 +            // accessed.
   1.177 +            //
   1.178 +            // The instance installs its own shutdown observers. So, we just
   1.179 +            // fire and forget: it will clean itself up.
   1.180 +            let reporter = this.healthReporter;
   1.181 +          }.bind(this),
   1.182 +        }, delayInterval, this.timer.TYPE_ONE_SHOT);
   1.183 +
   1.184 +        break;
   1.185 +
   1.186 +      case "quit-application":
   1.187 +        this._os.removeObserver(this, "quit-application");
   1.188 +        this._quitting = true;
   1.189 +
   1.190 +        // Shutdown doesn't clear pending timers. So, we need to explicitly
   1.191 +        // cancel our health reporter initialization timer or else it will
   1.192 +        // attempt initialization after shutdown has commenced. This would
   1.193 +        // likely lead to stalls or crashes.
   1.194 +        if (this.timer) {
   1.195 +          this.timer.cancel();
   1.196 +        }
   1.197 +
   1.198 +        if (this.policy) {
   1.199 +          this.policy.stopPolling();
   1.200 +        }
   1.201 +        break;
   1.202 +    }
   1.203 +  },
   1.204 +
   1.205 +  /**
   1.206 +   * The HealthReporter instance associated with this service.
   1.207 +   *
   1.208 +   * If the service is disabled, this will return null.
   1.209 +   *
   1.210 +   * The obtained instance may not be fully initialized.
   1.211 +   */
   1.212 +  get healthReporter() {
   1.213 +    if (!this._prefs.get("service.enabled", true)) {
   1.214 +      return null;
   1.215 +    }
   1.216 +
   1.217 +    if ("_healthReporter" in this) {
   1.218 +      return this._healthReporter;
   1.219 +    }
   1.220 +
   1.221 +    try {
   1.222 +      this._loadHealthReporter();
   1.223 +    } catch (ex) {
   1.224 +      this._healthReporter = null;
   1.225 +      Cu.reportError("Exception when obtaining health reporter: " +
   1.226 +                     CommonUtils.exceptionStr(ex));
   1.227 +    }
   1.228 +
   1.229 +    return this._healthReporter;
   1.230 +  },
   1.231 +
   1.232 +  _loadHealthReporter: function () {
   1.233 +    // This should never happen. It was added to help trace down bug 924307.
   1.234 +    if (!this.policy) {
   1.235 +      throw new Error("this.policy not set.");
   1.236 +    }
   1.237 +
   1.238 +    let ns = {};
   1.239 +    // Lazy import so application startup isn't adversely affected.
   1.240 +
   1.241 +    Cu.import("resource://gre/modules/Task.jsm", ns);
   1.242 +    Cu.import("resource://gre/modules/HealthReport.jsm", ns);
   1.243 +    Cu.import("resource://gre/modules/Log.jsm", ns);
   1.244 +
   1.245 +    // How many times will we rewrite this code before rolling it up into a
   1.246 +    // generic module? See also bug 451283.
   1.247 +    const LOGGERS = [
   1.248 +      "Services.DataReporting",
   1.249 +      "Services.HealthReport",
   1.250 +      "Services.Metrics",
   1.251 +      "Services.BagheeraClient",
   1.252 +      "Sqlite.Connection.healthreport",
   1.253 +    ];
   1.254 +
   1.255 +    let loggingPrefs = new Preferences(HEALTHREPORT_LOGGING_BRANCH);
   1.256 +    if (loggingPrefs.get("consoleEnabled", true)) {
   1.257 +      let level = loggingPrefs.get("consoleLevel", "Warn");
   1.258 +      let appender = new ns.Log.ConsoleAppender();
   1.259 +      appender.level = ns.Log.Level[level] || ns.Log.Level.Warn;
   1.260 +
   1.261 +      for (let name of LOGGERS) {
   1.262 +        let logger = ns.Log.repository.getLogger(name);
   1.263 +        logger.addAppender(appender);
   1.264 +      }
   1.265 +    }
   1.266 +
   1.267 +    if (loggingPrefs.get("dumpEnabled", false)) {
   1.268 +      let level = loggingPrefs.get("dumpLevel", "Debug");
   1.269 +      let appender = new ns.Log.DumpAppender();
   1.270 +      appender.level = ns.Log.Level[level] || ns.Log.Level.Debug;
   1.271 +
   1.272 +      for (let name of LOGGERS) {
   1.273 +        let logger = ns.Log.repository.getLogger(name);
   1.274 +        logger.addAppender(appender);
   1.275 +      }
   1.276 +    }
   1.277 +
   1.278 +    this._healthReporter = new ns.HealthReporter(HEALTHREPORT_BRANCH,
   1.279 +                                                 this.policy,
   1.280 +                                                 this.sessionRecorder);
   1.281 +
   1.282 +    // Wait for initialization to finish so if a shutdown occurs before init
   1.283 +    // has finished we don't adversely affect app startup on next run.
   1.284 +    this._healthReporter.init().then(function onInit() {
   1.285 +      this._prefs.set("service.firstRun", true);
   1.286 +    }.bind(this));
   1.287 +  },
   1.288 +});
   1.289 +
   1.290 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataReportingService]);
   1.291 +
   1.292 +#define MERGED_COMPARTMENT
   1.293 +
   1.294 +#include ../common/observers.js
   1.295 +;
   1.296 +#include policy.jsm
   1.297 +;
   1.298 +#include sessions.jsm
   1.299 +;
   1.300 +

mercurial