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 +