Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 Cu.import("resource://gre/modules/Preferences.jsm");
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
11 Cu.import("resource://services-common/utils.js");
14 const ROOT_BRANCH = "datareporting.";
15 const POLICY_BRANCH = ROOT_BRANCH + "policy.";
16 const SESSIONS_BRANCH = ROOT_BRANCH + "sessions.";
17 const HEALTHREPORT_BRANCH = ROOT_BRANCH + "healthreport.";
18 const HEALTHREPORT_LOGGING_BRANCH = HEALTHREPORT_BRANCH + "logging.";
19 const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
20 const DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC = 60 * 1000;
22 /**
23 * The Firefox Health Report XPCOM service.
24 *
25 * External consumers will be interested in the "reporter" property of this
26 * service. This property is a `HealthReporter` instance that powers the
27 * service. The property may be null if the Health Report service is not
28 * enabled.
29 *
30 * EXAMPLE USAGE
31 * =============
32 *
33 * let reporter = Cc["@mozilla.org/datareporting/service;1"]
34 * .getService(Ci.nsISupports)
35 * .wrappedJSObject
36 * .healthReporter;
37 *
38 * if (reporter.haveRemoteData) {
39 * // ...
40 * }
41 *
42 * IMPLEMENTATION NOTES
43 * ====================
44 *
45 * In order to not adversely impact application start time, the `HealthReporter`
46 * instance is not initialized until a few seconds after "final-ui-startup."
47 * The exact delay is configurable via preferences so it can be adjusted with
48 * a hotfix extension if the default value is ever problematic. Because of the
49 * overhead with the initial creation of the database, the first run is delayed
50 * even more than subsequent runs. This does mean that the first moments of
51 * browser activity may be lost by FHR.
52 *
53 * Shutdown of the `HealthReporter` instance is handled completely within the
54 * instance (it registers observers on initialization). See the notes on that
55 * type for more.
56 */
57 this.DataReportingService = function () {
58 this.wrappedJSObject = this;
60 this._quitting = false;
62 this._os = Cc["@mozilla.org/observer-service;1"]
63 .getService(Ci.nsIObserverService);
64 }
66 DataReportingService.prototype = Object.freeze({
67 classID: Components.ID("{41f6ae36-a79f-4613-9ac3-915e70f83789}"),
69 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
70 Ci.nsISupportsWeakReference]),
72 //---------------------------------------------
73 // Start of policy listeners.
74 //---------------------------------------------
76 /**
77 * Called when policy requests data upload.
78 */
79 onRequestDataUpload: function (request) {
80 if (!this.healthReporter) {
81 return;
82 }
84 this.healthReporter.requestDataUpload(request);
85 },
87 onNotifyDataPolicy: function (request) {
88 Observers.notify("datareporting:notify-data-policy:request", request);
89 },
91 onRequestRemoteDelete: function (request) {
92 if (!this.healthReporter) {
93 return;
94 }
96 this.healthReporter.deleteRemoteData(request);
97 },
99 //---------------------------------------------
100 // End of policy listeners.
101 //---------------------------------------------
103 observe: function observe(subject, topic, data) {
104 switch (topic) {
105 case "app-startup":
106 this._os.addObserver(this, "profile-after-change", true);
107 break;
109 case "profile-after-change":
110 this._os.removeObserver(this, "profile-after-change");
112 try {
113 this._prefs = new Preferences(HEALTHREPORT_BRANCH);
115 // We don't initialize the sessions recorder unless Health Report is
116 // around to provide pruning of data.
117 //
118 // FUTURE consider having the SessionsRecorder always enabled and/or
119 // living in its own XPCOM service.
120 if (this._prefs.get("service.enabled", true)) {
121 this.sessionRecorder = new SessionRecorder(SESSIONS_BRANCH);
122 this.sessionRecorder.onStartup();
123 }
125 // We can't interact with prefs until after the profile is present.
126 let policyPrefs = new Preferences(POLICY_BRANCH);
127 this.policy = new DataReportingPolicy(policyPrefs, this._prefs, this);
129 this._os.addObserver(this, "sessionstore-windows-restored", true);
130 } catch (ex) {
131 Cu.reportError("Exception when initializing data reporting service: " +
132 CommonUtils.exceptionStr(ex));
133 }
134 break;
136 case "sessionstore-windows-restored":
137 this._os.removeObserver(this, "sessionstore-windows-restored");
138 this._os.addObserver(this, "quit-application", false);
140 this.policy.startPolling();
142 // Don't initialize Firefox Health Reporter collection and submission
143 // service unless it is enabled.
144 if (!this._prefs.get("service.enabled", true)) {
145 return;
146 }
148 let haveFirstRun = this._prefs.get("service.firstRun", false);
149 let delayInterval;
151 if (haveFirstRun) {
152 delayInterval = this._prefs.get("service.loadDelayMsec") ||
153 DEFAULT_LOAD_DELAY_MSEC;
154 } else {
155 delayInterval = this._prefs.get("service.loadDelayFirstRunMsec") ||
156 DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC;
157 }
159 // Delay service loading a little more so things have an opportunity
160 // to cool down first.
161 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
162 this.timer.initWithCallback({
163 notify: function notify() {
164 delete this.timer;
166 // There could be a race between "quit-application" firing and
167 // this callback being invoked. We close that door.
168 if (this._quitting) {
169 return;
170 }
172 // Side effect: instantiates the reporter instance if not already
173 // accessed.
174 //
175 // The instance installs its own shutdown observers. So, we just
176 // fire and forget: it will clean itself up.
177 let reporter = this.healthReporter;
178 }.bind(this),
179 }, delayInterval, this.timer.TYPE_ONE_SHOT);
181 break;
183 case "quit-application":
184 this._os.removeObserver(this, "quit-application");
185 this._quitting = true;
187 // Shutdown doesn't clear pending timers. So, we need to explicitly
188 // cancel our health reporter initialization timer or else it will
189 // attempt initialization after shutdown has commenced. This would
190 // likely lead to stalls or crashes.
191 if (this.timer) {
192 this.timer.cancel();
193 }
195 if (this.policy) {
196 this.policy.stopPolling();
197 }
198 break;
199 }
200 },
202 /**
203 * The HealthReporter instance associated with this service.
204 *
205 * If the service is disabled, this will return null.
206 *
207 * The obtained instance may not be fully initialized.
208 */
209 get healthReporter() {
210 if (!this._prefs.get("service.enabled", true)) {
211 return null;
212 }
214 if ("_healthReporter" in this) {
215 return this._healthReporter;
216 }
218 try {
219 this._loadHealthReporter();
220 } catch (ex) {
221 this._healthReporter = null;
222 Cu.reportError("Exception when obtaining health reporter: " +
223 CommonUtils.exceptionStr(ex));
224 }
226 return this._healthReporter;
227 },
229 _loadHealthReporter: function () {
230 // This should never happen. It was added to help trace down bug 924307.
231 if (!this.policy) {
232 throw new Error("this.policy not set.");
233 }
235 let ns = {};
236 // Lazy import so application startup isn't adversely affected.
238 Cu.import("resource://gre/modules/Task.jsm", ns);
239 Cu.import("resource://gre/modules/HealthReport.jsm", ns);
240 Cu.import("resource://gre/modules/Log.jsm", ns);
242 // How many times will we rewrite this code before rolling it up into a
243 // generic module? See also bug 451283.
244 const LOGGERS = [
245 "Services.DataReporting",
246 "Services.HealthReport",
247 "Services.Metrics",
248 "Services.BagheeraClient",
249 "Sqlite.Connection.healthreport",
250 ];
252 let loggingPrefs = new Preferences(HEALTHREPORT_LOGGING_BRANCH);
253 if (loggingPrefs.get("consoleEnabled", true)) {
254 let level = loggingPrefs.get("consoleLevel", "Warn");
255 let appender = new ns.Log.ConsoleAppender();
256 appender.level = ns.Log.Level[level] || ns.Log.Level.Warn;
258 for (let name of LOGGERS) {
259 let logger = ns.Log.repository.getLogger(name);
260 logger.addAppender(appender);
261 }
262 }
264 if (loggingPrefs.get("dumpEnabled", false)) {
265 let level = loggingPrefs.get("dumpLevel", "Debug");
266 let appender = new ns.Log.DumpAppender();
267 appender.level = ns.Log.Level[level] || ns.Log.Level.Debug;
269 for (let name of LOGGERS) {
270 let logger = ns.Log.repository.getLogger(name);
271 logger.addAppender(appender);
272 }
273 }
275 this._healthReporter = new ns.HealthReporter(HEALTHREPORT_BRANCH,
276 this.policy,
277 this.sessionRecorder);
279 // Wait for initialization to finish so if a shutdown occurs before init
280 // has finished we don't adversely affect app startup on next run.
281 this._healthReporter.init().then(function onInit() {
282 this._prefs.set("service.firstRun", true);
283 }.bind(this));
284 },
285 });
287 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataReportingService]);
289 #define MERGED_COMPARTMENT
291 #include ../common/observers.js
292 ;
293 #include policy.jsm
294 ;
295 #include sessions.jsm
296 ;