mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1234522 - Remove services/datareporting. r=gfritzsche
This commit is contained in:
parent
bc2c8cb929
commit
441e819b1d
@ -2,6 +2,9 @@
|
||||
* 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/. */
|
||||
|
||||
const LOGGER_NAME = "Toolkit.Telemetry";
|
||||
const LOGGER_PREFIX = "DataNotificationInfoBar::";
|
||||
|
||||
/**
|
||||
* Represents an info bar that shows a data submission notification.
|
||||
*/
|
||||
@ -21,7 +24,7 @@ var gDataNotificationInfoBar = {
|
||||
get _log() {
|
||||
let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
|
||||
delete this._log;
|
||||
return this._log = Log.repository.getLogger("Services.DataReporting.InfoBar");
|
||||
return this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
|
@ -501,10 +501,6 @@
|
||||
@RESPATH@/components/nsINIProcessor.js
|
||||
@RESPATH@/components/nsPrompter.manifest
|
||||
@RESPATH@/components/nsPrompter.js
|
||||
#ifdef MOZ_DATA_REPORTING
|
||||
@RESPATH@/components/DataReporting.manifest
|
||||
@RESPATH@/components/DataReportingService.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
@RESPATH@/components/HealthReportComponents.manifest
|
||||
@RESPATH@/browser/components/SelfSupportService.manifest
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include ../../netwerk/base/security-prefs.js
|
||||
#include init/all.js
|
||||
#ifdef MOZ_DATA_REPORTING
|
||||
#include ../../services/datareporting/datareporting-prefs.js
|
||||
#include ../../toolkit/components/telemetry/datareporting-prefs.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
#if MOZ_WIDGET_TOOLKIT == android
|
||||
|
@ -1,16 +0,0 @@
|
||||
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
|
||||
# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
|
||||
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
|
||||
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
|
||||
# suite (comm): {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
|
||||
# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
|
||||
|
||||
# The Data Reporting Service drives collection and submission of metrics
|
||||
# and other useful data to Mozilla. It drives the display of the data
|
||||
# submission notification info bar and thus is required by Firefox Health
|
||||
# Report and Telemetry.
|
||||
|
||||
component {41f6ae36-a79f-4613-9ac3-915e70f83789} DataReportingService.js
|
||||
contract @mozilla.org/datareporting/service;1 {41f6ae36-a79f-4613-9ac3-915e70f83789}
|
||||
category app-startup DataReportingService service,@mozilla.org/datareporting/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
|
||||
|
@ -1,296 +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";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/ClientID.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
||||
"resource://gre/modules/Log.jsm");
|
||||
|
||||
const ROOT_BRANCH = "datareporting.";
|
||||
const POLICY_BRANCH = ROOT_BRANCH + "policy.";
|
||||
const HEALTHREPORT_BRANCH = ROOT_BRANCH + "healthreport.";
|
||||
const HEALTHREPORT_LOGGING_BRANCH = HEALTHREPORT_BRANCH + "logging.";
|
||||
const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
|
||||
const DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The Firefox Health Report XPCOM service.
|
||||
*
|
||||
* External consumers will be interested in the "reporter" property of this
|
||||
* service. This property is a `HealthReporter` instance that powers the
|
||||
* service. The property may be null if the Health Report service is not
|
||||
* enabled.
|
||||
*
|
||||
* EXAMPLE USAGE
|
||||
* =============
|
||||
*
|
||||
* let reporter = Cc["@mozilla.org/datareporting/service;1"]
|
||||
* .getService(Ci.nsISupports)
|
||||
* .wrappedJSObject
|
||||
* .healthReporter;
|
||||
*
|
||||
* if (reporter.haveRemoteData) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* IMPLEMENTATION NOTES
|
||||
* ====================
|
||||
*
|
||||
* In order to not adversely impact application start time, the `HealthReporter`
|
||||
* instance is not initialized until a few seconds after "final-ui-startup."
|
||||
* The exact delay is configurable via preferences so it can be adjusted with
|
||||
* a hotfix extension if the default value is ever problematic. Because of the
|
||||
* overhead with the initial creation of the database, the first run is delayed
|
||||
* even more than subsequent runs. This does mean that the first moments of
|
||||
* browser activity may be lost by FHR.
|
||||
*
|
||||
* Shutdown of the `HealthReporter` instance is handled completely within the
|
||||
* instance (it registers observers on initialization). See the notes on that
|
||||
* type for more.
|
||||
*/
|
||||
this.DataReportingService = function () {
|
||||
this.wrappedJSObject = this;
|
||||
|
||||
this._quitting = false;
|
||||
|
||||
this._os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
}
|
||||
|
||||
DataReportingService.prototype = Object.freeze({
|
||||
classID: Components.ID("{41f6ae36-a79f-4613-9ac3-915e70f83789}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
//---------------------------------------------
|
||||
// Start of policy listeners.
|
||||
//---------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when policy requests data upload.
|
||||
*/
|
||||
onRequestDataUpload: function (request) {
|
||||
if (!this.healthReporter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.healthReporter.requestDataUpload(request);
|
||||
},
|
||||
|
||||
onNotifyDataPolicy: function (request) {
|
||||
Observers.notify("datareporting:notify-data-policy:request", request);
|
||||
},
|
||||
|
||||
onRequestRemoteDelete: function (request) {
|
||||
if (!this.healthReporter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.healthReporter.deleteRemoteData(request);
|
||||
},
|
||||
|
||||
//---------------------------------------------
|
||||
// End of policy listeners.
|
||||
//---------------------------------------------
|
||||
|
||||
observe: function observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "app-startup":
|
||||
this._os.addObserver(this, "profile-after-change", true);
|
||||
break;
|
||||
|
||||
case "profile-after-change":
|
||||
this._os.removeObserver(this, "profile-after-change");
|
||||
|
||||
try {
|
||||
this._prefs = new Preferences(HEALTHREPORT_BRANCH);
|
||||
|
||||
// We can't interact with prefs until after the profile is present.
|
||||
let policyPrefs = new Preferences(POLICY_BRANCH);
|
||||
this.policy = new DataReportingPolicy(policyPrefs, this._prefs, this);
|
||||
|
||||
this._os.addObserver(this, "sessionstore-windows-restored", true);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Exception when initializing data reporting service: " +
|
||||
Log.exceptionStr(ex));
|
||||
}
|
||||
break;
|
||||
|
||||
case "sessionstore-windows-restored":
|
||||
this._os.removeObserver(this, "sessionstore-windows-restored");
|
||||
this._os.addObserver(this, "quit-application", false);
|
||||
|
||||
let policy = this.policy;
|
||||
policy.startPolling();
|
||||
|
||||
// Don't initialize Firefox Health Reporter collection and submission
|
||||
// service unless it is enabled.
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let haveFirstRun = this._prefs.get("service.firstRun", false);
|
||||
let delayInterval;
|
||||
|
||||
if (haveFirstRun) {
|
||||
delayInterval = this._prefs.get("service.loadDelayMsec") ||
|
||||
DEFAULT_LOAD_DELAY_MSEC;
|
||||
} else {
|
||||
delayInterval = this._prefs.get("service.loadDelayFirstRunMsec") ||
|
||||
DEFAULT_LOAD_DELAY_FIRST_RUN_MSEC;
|
||||
}
|
||||
|
||||
// Delay service loading a little more so things have an opportunity
|
||||
// to cool down first.
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.timer.initWithCallback({
|
||||
notify: function notify() {
|
||||
delete this.timer;
|
||||
|
||||
// There could be a race between "quit-application" firing and
|
||||
// this callback being invoked. We close that door.
|
||||
if (this._quitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Side effect: instantiates the reporter instance if not already
|
||||
// accessed.
|
||||
//
|
||||
// The instance installs its own shutdown observers. So, we just
|
||||
// fire and forget: it will clean itself up.
|
||||
let reporter = this.healthReporter;
|
||||
policy.ensureUserNotified();
|
||||
}.bind(this),
|
||||
}, delayInterval, this.timer.TYPE_ONE_SHOT);
|
||||
|
||||
break;
|
||||
|
||||
case "quit-application":
|
||||
this._os.removeObserver(this, "quit-application");
|
||||
this._quitting = true;
|
||||
|
||||
// Shutdown doesn't clear pending timers. So, we need to explicitly
|
||||
// cancel our health reporter initialization timer or else it will
|
||||
// attempt initialization after shutdown has commenced. This would
|
||||
// likely lead to stalls or crashes.
|
||||
if (this.timer) {
|
||||
this.timer.cancel();
|
||||
}
|
||||
|
||||
if (this.policy) {
|
||||
this.policy.stopPolling();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The HealthReporter instance associated with this service.
|
||||
*
|
||||
* If the service is disabled, this will return null.
|
||||
*
|
||||
* The obtained instance may not be fully initialized.
|
||||
*/
|
||||
get healthReporter() {
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("_healthReporter" in this) {
|
||||
return this._healthReporter;
|
||||
}
|
||||
|
||||
try {
|
||||
this._loadHealthReporter();
|
||||
} catch (ex) {
|
||||
this._healthReporter = null;
|
||||
Cu.reportError("Exception when obtaining health reporter: " +
|
||||
Log.exceptionStr(ex));
|
||||
}
|
||||
|
||||
return this._healthReporter;
|
||||
},
|
||||
|
||||
_loadHealthReporter: function () {
|
||||
// This should never happen. It was added to help trace down bug 924307.
|
||||
if (!this.policy) {
|
||||
throw new Error("this.policy not set.");
|
||||
}
|
||||
|
||||
let ns = {};
|
||||
// Lazy import so application startup isn't adversely affected.
|
||||
|
||||
Cu.import("resource://gre/modules/HealthReport.jsm", ns);
|
||||
|
||||
// How many times will we rewrite this code before rolling it up into a
|
||||
// generic module? See also bug 451283.
|
||||
const LOGGERS = [
|
||||
"Services.DataReporting",
|
||||
"Services.HealthReport",
|
||||
"Services.Metrics",
|
||||
"Services.BagheeraClient",
|
||||
"Sqlite.Connection.healthreport",
|
||||
];
|
||||
|
||||
let loggingPrefs = new Preferences(HEALTHREPORT_LOGGING_BRANCH);
|
||||
if (loggingPrefs.get("consoleEnabled", true)) {
|
||||
let level = loggingPrefs.get("consoleLevel", "Warn");
|
||||
let appender = new Log.ConsoleAppender();
|
||||
appender.level = Log.Level[level] || Log.Level.Warn;
|
||||
|
||||
for (let name of LOGGERS) {
|
||||
let logger = Log.repository.getLogger(name);
|
||||
logger.addAppender(appender);
|
||||
}
|
||||
}
|
||||
|
||||
if (loggingPrefs.get("dumpEnabled", false)) {
|
||||
let level = loggingPrefs.get("dumpLevel", "Debug");
|
||||
let appender = new Log.DumpAppender();
|
||||
appender.level = Log.Level[level] || Log.Level.Debug;
|
||||
|
||||
for (let name of LOGGERS) {
|
||||
let logger = Log.repository.getLogger(name);
|
||||
logger.addAppender(appender);
|
||||
}
|
||||
}
|
||||
|
||||
this._healthReporter = new ns.HealthReporter(HEALTHREPORT_BRANCH, this.policy);
|
||||
|
||||
// Wait for initialization to finish so if a shutdown occurs before init
|
||||
// has finished we don't adversely affect app startup on next run.
|
||||
this._healthReporter.init().then(function onInit() {
|
||||
this._prefs.set("service.firstRun", true);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* This returns a promise resolving to the the stable client ID we use for
|
||||
* data reporting (FHR & Telemetry). Previously exising FHR client IDs are
|
||||
* migrated to this.
|
||||
*
|
||||
* @return Promise<string> The stable client ID.
|
||||
*/
|
||||
getClientID: function() {
|
||||
return ClientID.getClientID();
|
||||
},
|
||||
});
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataReportingService]);
|
||||
|
||||
#define MERGED_COMPARTMENT
|
||||
|
||||
#include ../common/observers.js
|
||||
;
|
||||
#include policy.jsm
|
||||
;
|
||||
|
@ -1,52 +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 = ["MockPolicyListener"];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
|
||||
this.MockPolicyListener = function MockPolicyListener() {
|
||||
this._log = Log.repository.getLogger("Services.DataReporting.Testing.MockPolicyListener");
|
||||
this._log.level = Log.Level["Debug"];
|
||||
|
||||
this.requestDataUploadCount = 0;
|
||||
this.lastDataRequest = null;
|
||||
|
||||
this.requestRemoteDeleteCount = 0;
|
||||
this.lastRemoteDeleteRequest = null;
|
||||
|
||||
this.notifyUserCount = 0;
|
||||
this.lastNotifyRequest = null;
|
||||
}
|
||||
|
||||
MockPolicyListener.prototype = {
|
||||
onRequestDataUpload: function (request) {
|
||||
this._log.info("onRequestDataUpload invoked.");
|
||||
this.requestDataUploadCount++;
|
||||
this.lastDataRequest = request;
|
||||
},
|
||||
|
||||
onRequestRemoteDelete: function (request) {
|
||||
this._log.info("onRequestRemoteDelete invoked.");
|
||||
this.requestRemoteDeleteCount++;
|
||||
this.lastRemoteDeleteRequest = request;
|
||||
},
|
||||
|
||||
onNotifyDataPolicy: function (request, rejectMessage=null) {
|
||||
this._log.info("onNotifyDataPolicy invoked.");
|
||||
this.notifyUserCount++;
|
||||
this.lastNotifyRequest = request;
|
||||
if (rejectMessage) {
|
||||
request.onUserNotifyFailed(rejectMessage);
|
||||
} else {
|
||||
request.onUserNotifyComplete();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1,23 +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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'DataReporting.manifest',
|
||||
]
|
||||
|
||||
EXTRA_PP_COMPONENTS += [
|
||||
'DataReportingService.js',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES.services.datareporting += [
|
||||
'policy.jsm',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES.services.datareporting += [
|
||||
'modules-testing/mocks.jsm',
|
||||
]
|
@ -1,927 +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 is in transition. Most of its content needs to be moved under
|
||||
* /services/healthreport.
|
||||
*/
|
||||
|
||||
#ifndef MERGED_COMPARTMENT
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"DataSubmissionRequest", // For test use only.
|
||||
"DataReportingPolicy",
|
||||
"DATAREPORTING_POLICY_VERSION",
|
||||
];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
#endif
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
// The current policy version number. If the version number stored in the prefs
|
||||
// is smaller than this, data upload will be disabled until the user is re-notified
|
||||
// about the policy changes.
|
||||
const DATAREPORTING_POLICY_VERSION = 1;
|
||||
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
// Used as a sanity lower bound for dates stored in prefs. This module was
|
||||
// implemented in 2012, so any earlier dates indicate an incorrect clock.
|
||||
const OLDEST_ALLOWED_YEAR = 2012;
|
||||
|
||||
/**
|
||||
* Represents a request to display data policy.
|
||||
*
|
||||
* Receivers of these instances are expected to call one or more of the on*
|
||||
* functions when events occur.
|
||||
*
|
||||
* When one of these requests is received, the first thing a callee should do
|
||||
* is present notification to the user of the data policy. When the notice
|
||||
* is displayed to the user, the callee should call `onUserNotifyComplete`.
|
||||
*
|
||||
* If for whatever reason the callee could not display a notice,
|
||||
* it should call `onUserNotifyFailed`.
|
||||
*
|
||||
* @param policy
|
||||
* (DataReportingPolicy) The policy instance this request came from.
|
||||
* @param deferred
|
||||
* (deferred) The promise that will be fulfilled when display occurs.
|
||||
*/
|
||||
function NotifyPolicyRequest(policy, deferred) {
|
||||
this.policy = policy;
|
||||
this.deferred = deferred;
|
||||
}
|
||||
NotifyPolicyRequest.prototype = Object.freeze({
|
||||
/**
|
||||
* Called when the user is notified of the policy.
|
||||
*/
|
||||
onUserNotifyComplete: function () {
|
||||
return this.deferred.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when there was an error notifying the user about the policy.
|
||||
*
|
||||
* @param error
|
||||
* (Error) Explains what went wrong.
|
||||
*/
|
||||
onUserNotifyFailed: function (error) {
|
||||
return this.deferred.reject(error);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a request to submit data.
|
||||
*
|
||||
* Instances of this are created when the policy requests data upload or
|
||||
* deletion.
|
||||
*
|
||||
* Receivers are expected to call one of the provided on* functions to signal
|
||||
* completion of the request.
|
||||
*
|
||||
* Instances of this type should not be instantiated outside of this file.
|
||||
* Receivers of instances of this type should not attempt to do anything with
|
||||
* the instance except call one of the on* methods.
|
||||
*/
|
||||
this.DataSubmissionRequest = function (promise, expiresDate, isDelete) {
|
||||
this.promise = promise;
|
||||
this.expiresDate = expiresDate;
|
||||
this.isDelete = isDelete;
|
||||
|
||||
this.state = null;
|
||||
this.reason = null;
|
||||
}
|
||||
|
||||
this.DataSubmissionRequest.prototype = Object.freeze({
|
||||
NO_DATA_AVAILABLE: "no-data-available",
|
||||
SUBMISSION_SUCCESS: "success",
|
||||
SUBMISSION_FAILURE_SOFT: "failure-soft",
|
||||
SUBMISSION_FAILURE_HARD: "failure-hard",
|
||||
UPLOAD_IN_PROGRESS: "upload-in-progress",
|
||||
|
||||
/**
|
||||
* No submission was attempted because no data was available.
|
||||
*
|
||||
* In the case of upload, this means there is no data to upload (perhaps
|
||||
* it isn't available yet). In case of remote deletion, it means that there
|
||||
* is no remote data to delete.
|
||||
*/
|
||||
onNoDataAvailable: function onNoDataAvailable() {
|
||||
this.state = this.NO_DATA_AVAILABLE;
|
||||
this.promise.resolve(this);
|
||||
return this.promise.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Data submission has completed successfully.
|
||||
*
|
||||
* In case of upload, this means the upload completed successfully. In case
|
||||
* of deletion, the data was deleted successfully.
|
||||
*
|
||||
* @param date
|
||||
* (Date) When data submission occurred.
|
||||
*/
|
||||
onSubmissionSuccess: function onSubmissionSuccess(date) {
|
||||
this.state = this.SUBMISSION_SUCCESS;
|
||||
this.submissionDate = date;
|
||||
this.promise.resolve(this);
|
||||
return this.promise.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* There was a recoverable failure when submitting data.
|
||||
*
|
||||
* Perhaps the server was down. Perhaps the network wasn't available. The
|
||||
* policy may request submission again after a short delay.
|
||||
*
|
||||
* @param reason
|
||||
* (string) Why the failure occurred. For logging purposes only.
|
||||
*/
|
||||
onSubmissionFailureSoft: function onSubmissionFailureSoft(reason=null) {
|
||||
this.state = this.SUBMISSION_FAILURE_SOFT;
|
||||
this.reason = reason;
|
||||
this.promise.resolve(this);
|
||||
return this.promise.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* There was an unrecoverable failure when submitting data.
|
||||
*
|
||||
* Perhaps the client is misconfigured. Perhaps the server rejected the data.
|
||||
* Attempts at performing submission again will yield the same result. So,
|
||||
* the policy should not try again (until the next day).
|
||||
*
|
||||
* @param reason
|
||||
* (string) Why the failure occurred. For logging purposes only.
|
||||
*/
|
||||
onSubmissionFailureHard: function onSubmissionFailureHard(reason=null) {
|
||||
this.state = this.SUBMISSION_FAILURE_HARD;
|
||||
this.reason = reason;
|
||||
this.promise.resolve(this);
|
||||
return this.promise.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* The request was aborted because an upload was already in progress.
|
||||
*/
|
||||
onUploadInProgress: function (reason=null) {
|
||||
this.state = this.UPLOAD_IN_PROGRESS;
|
||||
this.reason = reason;
|
||||
this.promise.resolve(this);
|
||||
return this.promise.promise;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Manages scheduling of Firefox Health Report data submission.
|
||||
*
|
||||
* The rules of data submission are as follows:
|
||||
*
|
||||
* 1. Do not submit data more than once every 24 hours.
|
||||
* 2. Try to submit as close to 24 hours apart as possible.
|
||||
* 3. Do not submit too soon after application startup so as to not negatively
|
||||
* impact performance at startup.
|
||||
* 4. Before first ever data submission, the user should be notified about
|
||||
* data collection practices.
|
||||
* 5. User should have opportunity to react to this notification before
|
||||
* data submission.
|
||||
* 6. If data submission fails, try at most 2 additional times before giving
|
||||
* up on that day's submission.
|
||||
*
|
||||
* The listener passed into the instance must have the following properties
|
||||
* (which are callbacks that will be invoked at certain key events):
|
||||
*
|
||||
* * onRequestDataUpload(request) - Called when the policy is requesting
|
||||
* data to be submitted. The function is passed a `DataSubmissionRequest`.
|
||||
* The listener should call one of the special resolving functions on that
|
||||
* instance (see the documentation for that type).
|
||||
*
|
||||
* * onRequestRemoteDelete(request) - Called when the policy is requesting
|
||||
* deletion of remotely stored data. The function is passed a
|
||||
* `DataSubmissionRequest`. The listener should call one of the special
|
||||
* resolving functions on that instance (just like `onRequestDataUpload`).
|
||||
*
|
||||
* * onNotifyDataPolicy(request) - Called when the policy is requesting the
|
||||
* user to be notified that data submission will occur. The function
|
||||
* receives a `NotifyPolicyRequest` instance. The callee should call one or
|
||||
* more of the functions on that instance when specific events occur. See
|
||||
* the documentation for that type for more.
|
||||
*
|
||||
* Note that the notification method is abstracted. Different applications
|
||||
* can have different mechanisms by which they notify the user of data
|
||||
* submission practices.
|
||||
*
|
||||
* @param policyPrefs
|
||||
* (Preferences) Handle on preferences branch on which state will be
|
||||
* queried and stored.
|
||||
* @param healthReportPrefs
|
||||
* (Preferences) Handle on preferences branch holding Health Report state.
|
||||
* @param listener
|
||||
* (object) Object with callbacks that will be invoked at certain key
|
||||
* events.
|
||||
*/
|
||||
this.DataReportingPolicy = function (prefs, healthReportPrefs, listener) {
|
||||
this._log = Log.repository.getLogger("Services.DataReporting.Policy");
|
||||
this._log.level = Log.Level["Debug"];
|
||||
|
||||
for (let handler of this.REQUIRED_LISTENERS) {
|
||||
if (!listener[handler]) {
|
||||
throw new Error("Passed listener does not contain required handler: " +
|
||||
handler);
|
||||
}
|
||||
}
|
||||
|
||||
this._prefs = prefs;
|
||||
this._healthReportPrefs = healthReportPrefs;
|
||||
this._listener = listener;
|
||||
this._userNotifyPromise = null;
|
||||
|
||||
this._migratePrefs();
|
||||
|
||||
if (!this.firstRunDate.getTime()) {
|
||||
// If we've never run before, record the current time.
|
||||
this.firstRunDate = this.now();
|
||||
}
|
||||
|
||||
// Install an observer so that we can act on changes from external
|
||||
// code (such as Android UI).
|
||||
// Use a function because this is the only place where the Preferences
|
||||
// abstraction is way less usable than nsIPrefBranch.
|
||||
//
|
||||
// Hang on to the observer here so that tests can reach it.
|
||||
this.uploadEnabledObserver = function onUploadEnabledChanged() {
|
||||
if (this.pendingDeleteRemoteData || this.healthReportUploadEnabled) {
|
||||
// Nothing to do: either we're already deleting because the caller
|
||||
// came through the front door (rHRUE), or they set the flag to true.
|
||||
return;
|
||||
}
|
||||
this._log.info("uploadEnabled pref changed. Scheduling deletion.");
|
||||
this.deleteRemoteData();
|
||||
}.bind(this);
|
||||
|
||||
healthReportPrefs.observe("uploadEnabled", this.uploadEnabledObserver);
|
||||
|
||||
// Ensure we are scheduled to submit.
|
||||
if (!this.nextDataSubmissionDate.getTime()) {
|
||||
this.nextDataSubmissionDate = this._futureDate(MILLISECONDS_PER_DAY);
|
||||
}
|
||||
|
||||
// Record when we last requested for submitted data to be sent. This is
|
||||
// to avoid having multiple outstanding requests.
|
||||
this._inProgressSubmissionRequest = null;
|
||||
};
|
||||
|
||||
this.DataReportingPolicy.prototype = Object.freeze({
|
||||
/**
|
||||
* How often to poll to see if we need to do something.
|
||||
*
|
||||
* The interval needs to be short enough such that short-lived applications
|
||||
* have an opportunity to submit data. But, it also needs to be long enough
|
||||
* to not negatively impact performance.
|
||||
*
|
||||
* The random bit is to ensure that other systems scheduling around the same
|
||||
* interval don't all get scheduled together.
|
||||
*/
|
||||
POLL_INTERVAL_MSEC: (60 * 1000) + Math.floor(2.5 * 1000 * Math.random()),
|
||||
|
||||
/**
|
||||
* How long individual data submission requests live before expiring.
|
||||
*
|
||||
* Data submission requests have this long to complete before we give up on
|
||||
* them and try again.
|
||||
*
|
||||
* We want this to be short enough that we retry frequently enough but long
|
||||
* enough to give slow networks and systems time to handle it.
|
||||
*/
|
||||
SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC: 10 * 60 * 1000,
|
||||
|
||||
/**
|
||||
* Our backoff schedule in case of submission failure.
|
||||
*
|
||||
* This dictates both the number of times we retry a daily submission and
|
||||
* when to retry after each failure.
|
||||
*
|
||||
* Each element represents how long to wait after each recoverable failure.
|
||||
* After the first failure, we wait the time in element 0 before trying
|
||||
* again. After the second failure, we wait the time in element 1. Once
|
||||
* we run out of values in this array, we give up on that day's submission
|
||||
* and schedule for a day out.
|
||||
*/
|
||||
FAILURE_BACKOFF_INTERVALS: [
|
||||
15 * 60 * 1000,
|
||||
60 * 60 * 1000,
|
||||
],
|
||||
|
||||
REQUIRED_LISTENERS: [
|
||||
"onRequestDataUpload",
|
||||
"onRequestRemoteDelete",
|
||||
"onNotifyDataPolicy",
|
||||
],
|
||||
|
||||
/**
|
||||
* The first time the health report policy came into existence.
|
||||
*
|
||||
* This is used for scheduling of the initial submission.
|
||||
*/
|
||||
get firstRunDate() {
|
||||
return CommonUtils.getDatePref(this._prefs, "firstRunTime", 0, this._log,
|
||||
OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set firstRunDate(value) {
|
||||
this._log.debug("Setting first-run date: " + value);
|
||||
CommonUtils.setDatePref(this._prefs, "firstRunTime", value,
|
||||
OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
get dataSubmissionPolicyNotifiedDate() {
|
||||
return CommonUtils.getDatePref(this._prefs,
|
||||
"dataSubmissionPolicyNotifiedTime", 0,
|
||||
this._log, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set dataSubmissionPolicyNotifiedDate(value) {
|
||||
this._log.debug("Setting user notified date: " + value);
|
||||
CommonUtils.setDatePref(this._prefs, "dataSubmissionPolicyNotifiedTime",
|
||||
value, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
get dataSubmissionPolicyBypassNotification() {
|
||||
return this._prefs.get("dataSubmissionPolicyBypassNotification", false);
|
||||
},
|
||||
|
||||
set dataSubmissionPolicyBypassNotification(value) {
|
||||
return this._prefs.set("dataSubmissionPolicyBypassNotification", !!value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether submission of data is allowed.
|
||||
*
|
||||
* This is the master switch for remote server communication. If it is
|
||||
* false, we never request upload or deletion.
|
||||
*/
|
||||
get dataSubmissionEnabled() {
|
||||
// Default is true because we are opt-out.
|
||||
return this._prefs.get("dataSubmissionEnabled", true);
|
||||
},
|
||||
|
||||
set dataSubmissionEnabled(value) {
|
||||
this._prefs.set("dataSubmissionEnabled", !!value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether submission of data is allowed for v2.
|
||||
*
|
||||
* This is used to gently turn off data submission for FHR v2 in Firefox 42+.
|
||||
*/
|
||||
get dataSubmissionEnabledV2() {
|
||||
// Default is true because we are opt-out.
|
||||
return this._prefs.get("dataSubmissionEnabled.v2", true);
|
||||
},
|
||||
|
||||
get currentPolicyVersion() {
|
||||
return this._prefs.get("currentPolicyVersion", DATAREPORTING_POLICY_VERSION);
|
||||
},
|
||||
|
||||
/**
|
||||
* The minimum policy version which for dataSubmissionPolicyAccepted to
|
||||
* to be valid.
|
||||
*/
|
||||
get minimumPolicyVersion() {
|
||||
// First check if the current channel has an ove
|
||||
let channel = UpdateUtils.getUpdateChannel(false);
|
||||
let channelPref = this._prefs.get("minimumPolicyVersion.channel-" + channel);
|
||||
return channelPref !== undefined ?
|
||||
channelPref : this._prefs.get("minimumPolicyVersion", 1);
|
||||
},
|
||||
|
||||
get dataSubmissionPolicyAcceptedVersion() {
|
||||
return this._prefs.get("dataSubmissionPolicyAcceptedVersion", 0);
|
||||
},
|
||||
|
||||
set dataSubmissionPolicyAcceptedVersion(value) {
|
||||
this._prefs.set("dataSubmissionPolicyAcceptedVersion", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks to see if the user has been notified about data submission
|
||||
* @return {bool}
|
||||
*/
|
||||
get userNotifiedOfCurrentPolicy() {
|
||||
return this.dataSubmissionPolicyNotifiedDate.getTime() > 0 &&
|
||||
this.dataSubmissionPolicyAcceptedVersion >= this.currentPolicyVersion;
|
||||
},
|
||||
|
||||
/**
|
||||
* When this policy last requested data submission.
|
||||
*
|
||||
* This is used mainly for forensics purposes and should have no bearing
|
||||
* on scheduling or run-time behavior.
|
||||
*/
|
||||
get lastDataSubmissionRequestedDate() {
|
||||
return CommonUtils.getDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionRequestedTime", 0,
|
||||
this._log, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set lastDataSubmissionRequestedDate(value) {
|
||||
CommonUtils.setDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionRequestedTime",
|
||||
value, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
/**
|
||||
* When the last data submission actually occurred.
|
||||
*
|
||||
* This is used mainly for forensics purposes and should have no bearing on
|
||||
* actual scheduling.
|
||||
*/
|
||||
get lastDataSubmissionSuccessfulDate() {
|
||||
return CommonUtils.getDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionSuccessfulTime", 0,
|
||||
this._log, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set lastDataSubmissionSuccessfulDate(value) {
|
||||
CommonUtils.setDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionSuccessfulTime",
|
||||
value, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
/**
|
||||
* When we last encountered a submission failure.
|
||||
*
|
||||
* This is used for forensics purposes and should have no bearing on
|
||||
* scheduling.
|
||||
*/
|
||||
get lastDataSubmissionFailureDate() {
|
||||
return CommonUtils.getDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionFailureTime",
|
||||
0, this._log, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set lastDataSubmissionFailureDate(value) {
|
||||
CommonUtils.setDatePref(this._healthReportPrefs,
|
||||
"lastDataSubmissionFailureTime",
|
||||
value, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
/**
|
||||
* When the next data submission is scheduled to occur.
|
||||
*
|
||||
* This is maintained internally by this type. External users should not
|
||||
* mutate this value.
|
||||
*/
|
||||
get nextDataSubmissionDate() {
|
||||
return CommonUtils.getDatePref(this._healthReportPrefs,
|
||||
"nextDataSubmissionTime", 0,
|
||||
this._log, OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
set nextDataSubmissionDate(value) {
|
||||
CommonUtils.setDatePref(this._healthReportPrefs,
|
||||
"nextDataSubmissionTime", value,
|
||||
OLDEST_ALLOWED_YEAR);
|
||||
},
|
||||
|
||||
/**
|
||||
* The number of submission failures for this day's upload.
|
||||
*
|
||||
* This is used to drive backoff and scheduling.
|
||||
*/
|
||||
get currentDaySubmissionFailureCount() {
|
||||
let v = this._healthReportPrefs.get("currentDaySubmissionFailureCount", 0);
|
||||
|
||||
if (!Number.isInteger(v)) {
|
||||
v = 0;
|
||||
}
|
||||
|
||||
return v;
|
||||
},
|
||||
|
||||
set currentDaySubmissionFailureCount(value) {
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error("Value must be integer: " + value);
|
||||
}
|
||||
|
||||
this._healthReportPrefs.set("currentDaySubmissionFailureCount", value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether a request to delete remote data is awaiting completion.
|
||||
*
|
||||
* If this is true, the policy will request that remote data be deleted.
|
||||
* Furthermore, no new data will be uploaded (if it's even allowed) until
|
||||
* the remote deletion is fulfilled.
|
||||
*/
|
||||
get pendingDeleteRemoteData() {
|
||||
return !!this._healthReportPrefs.get("pendingDeleteRemoteData", false);
|
||||
},
|
||||
|
||||
set pendingDeleteRemoteData(value) {
|
||||
this._healthReportPrefs.set("pendingDeleteRemoteData", !!value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether upload of Firefox Health Report data is enabled.
|
||||
*/
|
||||
get healthReportUploadEnabled() {
|
||||
return !!this._healthReportPrefs.get("uploadEnabled", true);
|
||||
},
|
||||
|
||||
// External callers should update this via `recordHealthReportUploadEnabled`
|
||||
// to ensure appropriate side-effects are performed.
|
||||
set healthReportUploadEnabled(value) {
|
||||
this._healthReportPrefs.set("uploadEnabled", !!value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the FHR upload enabled setting is locked and can't be changed.
|
||||
*/
|
||||
get healthReportUploadLocked() {
|
||||
return this._healthReportPrefs.locked("uploadEnabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* Record the user's intent for whether FHR should upload data.
|
||||
*
|
||||
* This is the preferred way for XUL applications to record a user's
|
||||
* preference on whether Firefox Health Report should upload data to
|
||||
* a server.
|
||||
*
|
||||
* If upload is disabled through this API, a request for remote data
|
||||
* deletion is initiated automatically.
|
||||
*
|
||||
* If upload is being disabled and this operation is scheduled to
|
||||
* occur immediately, a promise will be returned. This promise will be
|
||||
* fulfilled when the deletion attempt finishes. If upload is being
|
||||
* disabled and a promise is not returned, callers must poll
|
||||
* `haveRemoteData` on the HealthReporter instance to see if remote
|
||||
* data has been deleted.
|
||||
*
|
||||
* @param flag
|
||||
* (bool) Whether data submission is enabled or disabled.
|
||||
* @param reason
|
||||
* (string) Why this value is being adjusted. For logging
|
||||
* purposes only.
|
||||
*/
|
||||
recordHealthReportUploadEnabled: function (flag, reason="no-reason") {
|
||||
let result = null;
|
||||
if (!flag) {
|
||||
result = this.deleteRemoteData(reason);
|
||||
}
|
||||
|
||||
this.healthReportUploadEnabled = flag;
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Request that remote data be deleted.
|
||||
*
|
||||
* This will record an intent that previously uploaded data is to be deleted.
|
||||
* The policy will eventually issue a request to the listener for data
|
||||
* deletion. It will keep asking for deletion until the listener acknowledges
|
||||
* that data has been deleted.
|
||||
*/
|
||||
deleteRemoteData: function deleteRemoteData(reason="no-reason") {
|
||||
this._log.info("Remote data deletion requested: " + reason);
|
||||
|
||||
this.pendingDeleteRemoteData = true;
|
||||
|
||||
// We want delete deletion to occur as soon as possible. Move up any
|
||||
// pending scheduled data submission and try to trigger.
|
||||
this.nextDataSubmissionDate = this.now();
|
||||
return this.checkStateAndTrigger();
|
||||
},
|
||||
|
||||
/**
|
||||
* Start background polling for activity.
|
||||
*
|
||||
* This will set up a recurring timer that will periodically check if
|
||||
* activity is warranted.
|
||||
*
|
||||
* You typically call this function for each constructed instance.
|
||||
*/
|
||||
startPolling: function startPolling() {
|
||||
this.stopPolling();
|
||||
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.initWithCallback({
|
||||
notify: function notify() {
|
||||
this.checkStateAndTrigger();
|
||||
}.bind(this)
|
||||
}, this.POLL_INTERVAL_MSEC, this._timer.TYPE_REPEATING_SLACK);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop background polling for activity.
|
||||
*
|
||||
* This should be called when the instance is no longer needed.
|
||||
*/
|
||||
stopPolling: function stopPolling() {
|
||||
if (this._timer) {
|
||||
this._timer.cancel();
|
||||
this._timer = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Abstraction for obtaining current time.
|
||||
*
|
||||
* The purpose of this is to facilitate testing. Testing code can monkeypatch
|
||||
* this on instances instead of modifying the singleton Date object.
|
||||
*/
|
||||
now: function now() {
|
||||
return new Date();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check state and trigger actions, if necessary.
|
||||
*
|
||||
* This is what enforces the submission and notification policy detailed
|
||||
* above. You can think of this as the driver for health report data
|
||||
* submission.
|
||||
*
|
||||
* Typically this function is called automatically by the background polling.
|
||||
* But, it can safely be called manually as needed.
|
||||
*/
|
||||
checkStateAndTrigger: function checkStateAndTrigger() {
|
||||
// If the master data submission kill switch is toggled, we have nothing
|
||||
// to do. We don't notify about data policies because this would have
|
||||
// no effect.
|
||||
if (!this.dataSubmissionEnabled || !this.dataSubmissionEnabledV2) {
|
||||
this._log.debug("Data submission is disabled. Doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
let now = this.now();
|
||||
let nowT = now.getTime();
|
||||
let nextSubmissionDate = this.nextDataSubmissionDate;
|
||||
|
||||
// If the system clock were ever set to a time in the distant future,
|
||||
// it's possible our next schedule date is far out as well. We know
|
||||
// we shouldn't schedule for more than a day out, so we reset the next
|
||||
// scheduled date appropriately. 3 days was chosen arbitrarily.
|
||||
if (nextSubmissionDate.getTime() >= nowT + 3 * MILLISECONDS_PER_DAY) {
|
||||
this._log.warn("Next data submission time is far away. Was the system " +
|
||||
"clock recently readjusted? " + nextSubmissionDate);
|
||||
|
||||
// It shouldn't really matter what we set this to. 1 day in the future
|
||||
// should be pretty safe.
|
||||
this._moveScheduleForward24h();
|
||||
|
||||
// Fall through since we may have other actions.
|
||||
}
|
||||
|
||||
// Tend to any in progress work.
|
||||
if (this._processInProgressSubmission()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Requests to delete remote data take priority above everything else.
|
||||
if (this.pendingDeleteRemoteData) {
|
||||
if (nowT < nextSubmissionDate.getTime()) {
|
||||
this._log.debug("Deletion request is scheduled for the future: " +
|
||||
nextSubmissionDate);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._dispatchSubmissionRequest("onRequestRemoteDelete", true);
|
||||
}
|
||||
|
||||
if (!this.healthReportUploadEnabled) {
|
||||
this._log.debug("Data upload is disabled. Doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.ensureUserNotified()) {
|
||||
this._log.warn("The user has not been notified about the data submission " +
|
||||
"policy. Not attempting upload.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Data submission is allowed to occur. Now comes the scheduling part.
|
||||
|
||||
if (nowT < nextSubmissionDate.getTime()) {
|
||||
this._log.debug("Next data submission is scheduled in the future: " +
|
||||
nextSubmissionDate);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._dispatchSubmissionRequest("onRequestDataUpload", false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure that the data policy notification has been displayed.
|
||||
*
|
||||
* This must be called before data submission. If the policy has not been
|
||||
* displayed, data submission must not occur.
|
||||
*
|
||||
* @return bool Whether the notification has been displayed.
|
||||
*/
|
||||
ensureUserNotified: function () {
|
||||
if (this.userNotifiedOfCurrentPolicy || this.dataSubmissionPolicyBypassNotification) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The user has not been notified yet, but is in the process of being notified.
|
||||
if (this._userNotifyPromise) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
deferred.promise.then((function onSuccess() {
|
||||
this._recordDataPolicyNotification(this.now(), this.currentPolicyVersion);
|
||||
this._userNotifyPromise = null;
|
||||
}).bind(this), ((error) => {
|
||||
this._log.warn("Data policy notification presentation failed", error);
|
||||
this._userNotifyPromise = null;
|
||||
}).bind(this));
|
||||
|
||||
this._log.info("Requesting display of data policy.");
|
||||
let request = new NotifyPolicyRequest(this, deferred);
|
||||
try {
|
||||
this._listener.onNotifyDataPolicy(request);
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception when calling onNotifyDataPolicy", ex);
|
||||
}
|
||||
|
||||
this._userNotifyPromise = deferred.promise;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_recordDataPolicyNotification: function (date, version) {
|
||||
this._log.debug("Recording data policy notification to version " + version +
|
||||
" on date " + date);
|
||||
this.dataSubmissionPolicyNotifiedDate = date;
|
||||
this.dataSubmissionPolicyAcceptedVersion = version;
|
||||
},
|
||||
|
||||
_migratePrefs: function () {
|
||||
// Current prefs are mostly the same than the old ones, except for some deprecated ones.
|
||||
this._prefs.reset([
|
||||
"dataSubmissionPolicyAccepted",
|
||||
"dataSubmissionPolicyBypassAcceptance",
|
||||
"dataSubmissionPolicyResponseType",
|
||||
"dataSubmissionPolicyResponseTime"
|
||||
]);
|
||||
},
|
||||
|
||||
_processInProgressSubmission: function _processInProgressSubmission() {
|
||||
if (!this._inProgressSubmissionRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let now = this.now().getTime();
|
||||
if (this._inProgressSubmissionRequest.expiresDate.getTime() > now) {
|
||||
this._log.info("Waiting on in-progress submission request to finish.");
|
||||
return true;
|
||||
}
|
||||
|
||||
this._log.warn("Old submission request has expired from no activity.");
|
||||
this._inProgressSubmissionRequest.promise.reject(new Error("Request has expired."));
|
||||
this._inProgressSubmissionRequest = null;
|
||||
this._handleSubmissionFailure();
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
_dispatchSubmissionRequest: function _dispatchSubmissionRequest(handler, isDelete) {
|
||||
let now = this.now();
|
||||
|
||||
// We're past our scheduled next data submission date, so let's do it!
|
||||
this.lastDataSubmissionRequestedDate = now;
|
||||
let deferred = Promise.defer();
|
||||
let requestExpiresDate =
|
||||
this._futureDate(this.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC);
|
||||
this._inProgressSubmissionRequest = new DataSubmissionRequest(deferred,
|
||||
requestExpiresDate,
|
||||
isDelete);
|
||||
|
||||
let onSuccess = function onSuccess(result) {
|
||||
this._inProgressSubmissionRequest = null;
|
||||
this._handleSubmissionResult(result);
|
||||
}.bind(this);
|
||||
|
||||
let onError = function onError(error) {
|
||||
this._log.error("Error when handling data submission result", error);
|
||||
this._inProgressSubmissionRequest = null;
|
||||
this._handleSubmissionFailure();
|
||||
}.bind(this);
|
||||
|
||||
let chained = deferred.promise.then(onSuccess, onError);
|
||||
|
||||
this._log.info("Requesting data submission. Will expire at " +
|
||||
requestExpiresDate);
|
||||
try {
|
||||
let promise = this._listener[handler](this._inProgressSubmissionRequest);
|
||||
chained = chained.then(() => promise, null);
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception when calling " + handler, ex);
|
||||
this._inProgressSubmissionRequest = null;
|
||||
this._handleSubmissionFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
return chained;
|
||||
},
|
||||
|
||||
_handleSubmissionResult: function _handleSubmissionResult(request) {
|
||||
let state = request.state;
|
||||
let reason = request.reason || "no reason";
|
||||
this._log.info("Got submission request result: " + state);
|
||||
|
||||
if (state == request.SUBMISSION_SUCCESS) {
|
||||
if (request.isDelete) {
|
||||
this.pendingDeleteRemoteData = false;
|
||||
this._log.info("Successful data delete reported.");
|
||||
} else {
|
||||
this._log.info("Successful data upload reported.");
|
||||
}
|
||||
|
||||
this.lastDataSubmissionSuccessfulDate = request.submissionDate;
|
||||
|
||||
let nextSubmissionDate =
|
||||
new Date(request.submissionDate.getTime() + MILLISECONDS_PER_DAY);
|
||||
|
||||
// Schedule pending deletes immediately. This has potential to overload
|
||||
// the server. However, the frequency of delete requests across all
|
||||
// clients should be low, so this shouldn't pose a problem.
|
||||
if (this.pendingDeleteRemoteData) {
|
||||
nextSubmissionDate = this.now();
|
||||
}
|
||||
|
||||
this.nextDataSubmissionDate = nextSubmissionDate;
|
||||
this.currentDaySubmissionFailureCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == request.NO_DATA_AVAILABLE) {
|
||||
if (request.isDelete) {
|
||||
this._log.info("Remote data delete requested but no remote data was stored.");
|
||||
this.pendingDeleteRemoteData = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.info("No data was available to submit. May try later.");
|
||||
this._handleSubmissionFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't special case request.isDelete for these failures because it
|
||||
// likely means there was a server error.
|
||||
|
||||
if (state == request.SUBMISSION_FAILURE_SOFT) {
|
||||
this._log.warn("Soft error submitting data: " + reason);
|
||||
this.lastDataSubmissionFailureDate = this.now();
|
||||
this._handleSubmissionFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == request.SUBMISSION_FAILURE_HARD) {
|
||||
this._log.warn("Hard error submitting data: " + reason);
|
||||
this.lastDataSubmissionFailureDate = this.now();
|
||||
this._moveScheduleForward24h();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("Unknown state on DataSubmissionRequest: " + request.state);
|
||||
},
|
||||
|
||||
_handleSubmissionFailure: function _handleSubmissionFailure() {
|
||||
if (this.currentDaySubmissionFailureCount >= this.FAILURE_BACKOFF_INTERVALS.length) {
|
||||
this._log.warn("Reached the limit of daily submission attempts. " +
|
||||
"Rescheduling for tomorrow.");
|
||||
this._moveScheduleForward24h();
|
||||
return false;
|
||||
}
|
||||
|
||||
let offset = this.FAILURE_BACKOFF_INTERVALS[this.currentDaySubmissionFailureCount];
|
||||
this.nextDataSubmissionDate = this._futureDate(offset);
|
||||
this.currentDaySubmissionFailureCount++;
|
||||
return true;
|
||||
},
|
||||
|
||||
_moveScheduleForward24h: function _moveScheduleForward24h() {
|
||||
let d = this._futureDate(MILLISECONDS_PER_DAY);
|
||||
this._log.info("Setting next scheduled data submission for " + d);
|
||||
|
||||
this.nextDataSubmissionDate = d;
|
||||
this.currentDaySubmissionFailureCount = 0;
|
||||
},
|
||||
|
||||
_futureDate: function _futureDate(offset) {
|
||||
return new Date(this.now().getTime() + offset);
|
||||
},
|
||||
});
|
||||
|
@ -1,16 +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 initTestingInfrastructure() {
|
||||
let ns = {};
|
||||
Components.utils.import("resource://testing-common/services/common/logging.js",
|
||||
ns);
|
||||
|
||||
ns.initTestLogging();
|
||||
}).call(this);
|
||||
|
@ -1,689 +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/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
|
||||
Cu.import("resource://testing-common/services/datareporting/mocks.jsm");
|
||||
Cu.import("resource://gre/modules/UpdateUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
function getPolicy(name,
|
||||
aCurrentPolicyVersion = 1,
|
||||
aMinimumPolicyVersion = 1,
|
||||
aBranchMinimumVersionOverride) {
|
||||
let branch = "testing.datareporting." + name;
|
||||
|
||||
// The version prefs should not be removed on reset, so set them in the
|
||||
// default branch.
|
||||
let defaultPolicyPrefs = new Preferences({ branch: branch + ".policy."
|
||||
, defaultBranch: true });
|
||||
defaultPolicyPrefs.set("currentPolicyVersion", aCurrentPolicyVersion);
|
||||
defaultPolicyPrefs.set("minimumPolicyVersion", aMinimumPolicyVersion);
|
||||
let branchOverridePrefName = "minimumPolicyVersion.channel-" + UpdateUtils.getUpdateChannel(false);
|
||||
if (aBranchMinimumVersionOverride !== undefined)
|
||||
defaultPolicyPrefs.set(branchOverridePrefName, aBranchMinimumVersionOverride);
|
||||
else
|
||||
defaultPolicyPrefs.reset(branchOverridePrefName);
|
||||
|
||||
let policyPrefs = new Preferences(branch + ".policy.");
|
||||
let healthReportPrefs = new Preferences(branch + ".healthreport.");
|
||||
|
||||
let listener = new MockPolicyListener();
|
||||
let policy = new DataReportingPolicy(policyPrefs, healthReportPrefs, listener);
|
||||
|
||||
return [policy, policyPrefs, healthReportPrefs, listener];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the notification has been displayed to the user therefore having
|
||||
* policy.ensureUserNotified() === true, which will allow for a successful
|
||||
* data upload and afterwards does a call to policy.checkStateAndTrigger()
|
||||
* @param {Policy} policy
|
||||
* @return {Promise}
|
||||
*/
|
||||
function ensureUserNotifiedAndTrigger(policy) {
|
||||
return Task.spawn(function* ensureUserNotifiedAndTrigger () {
|
||||
policy.ensureUserNotified();
|
||||
yield policy._listener.lastNotifyRequest.deferred.promise;
|
||||
do_check_true(policy.userNotifiedOfCurrentPolicy);
|
||||
policy.checkStateAndTrigger();
|
||||
});
|
||||
}
|
||||
|
||||
function defineNow(policy, now) {
|
||||
print("Adjusting fake system clock to " + now);
|
||||
Object.defineProperty(policy, "now", {
|
||||
value: function customNow() {
|
||||
return now;
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let policyPrefs = new Preferences("foo.bar.policy.");
|
||||
let hrPrefs = new Preferences("foo.bar.healthreport.");
|
||||
let listener = {
|
||||
onRequestDataUpload: function() {},
|
||||
onRequestRemoteDelete: function() {},
|
||||
onNotifyDataPolicy: function() {},
|
||||
};
|
||||
|
||||
let policy = new DataReportingPolicy(policyPrefs, hrPrefs, listener);
|
||||
do_check_true(Date.now() - policy.firstRunDate.getTime() < 1000);
|
||||
|
||||
let tomorrow = Date.now() + 24 * 60 * 60 * 1000;
|
||||
do_check_true(tomorrow - policy.nextDataSubmissionDate.getTime() < 1000);
|
||||
|
||||
do_check_eq(policy.dataSubmissionPolicyAcceptedVersion, 0);
|
||||
do_check_false(policy.userNotifiedOfCurrentPolicy);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_prefs() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("prefs");
|
||||
|
||||
let now = new Date();
|
||||
let nowT = now.getTime();
|
||||
|
||||
policy.firstRunDate = now;
|
||||
do_check_eq(policyPrefs.get("firstRunTime"), nowT);
|
||||
do_check_eq(policy.firstRunDate.getTime(), nowT);
|
||||
|
||||
policy.dataSubmissionPolicyNotifiedDate = now;
|
||||
do_check_eq(policyPrefs.get("dataSubmissionPolicyNotifiedTime"), nowT);
|
||||
do_check_neq(policy.dataSubmissionPolicyNotifiedDate, null);
|
||||
do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), nowT);
|
||||
|
||||
policy.dataSubmissionEnabled = false;
|
||||
do_check_false(policyPrefs.get("dataSubmissionEnabled", true));
|
||||
do_check_false(policy.dataSubmissionEnabled);
|
||||
|
||||
let new_version = DATAREPORTING_POLICY_VERSION + 1;
|
||||
policy.dataSubmissionPolicyAcceptedVersion = new_version;
|
||||
do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), new_version);
|
||||
|
||||
do_check_false(policy.dataSubmissionPolicyBypassNotification);
|
||||
policy.dataSubmissionPolicyBypassNotification = true;
|
||||
do_check_true(policy.dataSubmissionPolicyBypassNotification);
|
||||
do_check_true(policyPrefs.get("dataSubmissionPolicyBypassNotification"));
|
||||
|
||||
policy.lastDataSubmissionRequestedDate = now;
|
||||
do_check_eq(hrPrefs.get("lastDataSubmissionRequestedTime"), nowT);
|
||||
do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), nowT);
|
||||
|
||||
policy.lastDataSubmissionSuccessfulDate = now;
|
||||
do_check_eq(hrPrefs.get("lastDataSubmissionSuccessfulTime"), nowT);
|
||||
do_check_eq(policy.lastDataSubmissionSuccessfulDate.getTime(), nowT);
|
||||
|
||||
policy.lastDataSubmissionFailureDate = now;
|
||||
do_check_eq(hrPrefs.get("lastDataSubmissionFailureTime"), nowT);
|
||||
do_check_eq(policy.lastDataSubmissionFailureDate.getTime(), nowT);
|
||||
|
||||
policy.nextDataSubmissionDate = now;
|
||||
do_check_eq(hrPrefs.get("nextDataSubmissionTime"), nowT);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(), nowT);
|
||||
|
||||
policy.currentDaySubmissionFailureCount = 2;
|
||||
do_check_eq(hrPrefs.get("currentDaySubmissionFailureCount", 0), 2);
|
||||
do_check_eq(policy.currentDaySubmissionFailureCount, 2);
|
||||
|
||||
policy.pendingDeleteRemoteData = true;
|
||||
do_check_true(hrPrefs.get("pendingDeleteRemoteData"));
|
||||
do_check_true(policy.pendingDeleteRemoteData);
|
||||
|
||||
policy.healthReportUploadEnabled = false;
|
||||
do_check_false(hrPrefs.get("uploadEnabled"));
|
||||
do_check_false(policy.healthReportUploadEnabled);
|
||||
|
||||
do_check_false(policy.healthReportUploadLocked);
|
||||
hrPrefs.lock("uploadEnabled");
|
||||
do_check_true(policy.healthReportUploadLocked);
|
||||
hrPrefs.unlock("uploadEnabled");
|
||||
do_check_false(policy.healthReportUploadLocked);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_migratePrefs () {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("migratePrefs");
|
||||
let outdated_prefs = {
|
||||
dataSubmissionPolicyAccepted: true,
|
||||
dataSubmissionPolicyBypassAcceptance: true,
|
||||
dataSubmissionPolicyResponseType: "something",
|
||||
dataSubmissionPolicyResponseTime: Date.now() + "",
|
||||
};
|
||||
|
||||
// Test removal of old prefs.
|
||||
for (let name in outdated_prefs) {
|
||||
policyPrefs.set(name, outdated_prefs[name]);
|
||||
}
|
||||
policy._migratePrefs();
|
||||
for (let name in outdated_prefs) {
|
||||
do_check_false(policyPrefs.has(name));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function test_userNotifiedOfCurrentPolicy () {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("initial_submission_notification");
|
||||
|
||||
do_check_false(policy.userNotifiedOfCurrentPolicy,
|
||||
"The initial state should be unnotified.");
|
||||
do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0);
|
||||
|
||||
policy.dataSubmissionPolicyAcceptedVersion = DATAREPORTING_POLICY_VERSION;
|
||||
do_check_false(policy.userNotifiedOfCurrentPolicy,
|
||||
"The default state of the date should have a time of 0 and it should therefore fail");
|
||||
do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0,
|
||||
"Updating the accepted version should not set a notified date.");
|
||||
|
||||
policy._recordDataPolicyNotification(new Date(), DATAREPORTING_POLICY_VERSION);
|
||||
do_check_true(policy.userNotifiedOfCurrentPolicy,
|
||||
"Using the proper API causes user notification to report as true.");
|
||||
|
||||
// It is assumed that later versions of the policy will incorporate previous
|
||||
// ones, therefore this should also return true.
|
||||
policy._recordDataPolicyNotification(new Date(), DATAREPORTING_POLICY_VERSION);
|
||||
policy.dataSubmissionPolicyAcceptedVersion = DATAREPORTING_POLICY_VERSION + 1;
|
||||
do_check_true(policy.userNotifiedOfCurrentPolicy, 'A future version of the policy should pass.');
|
||||
|
||||
policy._recordDataPolicyNotification(new Date(), DATAREPORTING_POLICY_VERSION);
|
||||
policy.dataSubmissionPolicyAcceptedVersion = DATAREPORTING_POLICY_VERSION - 1;
|
||||
do_check_false(policy.userNotifiedOfCurrentPolicy, 'A previous version of the policy should fail.');
|
||||
});
|
||||
|
||||
add_task(function* test_notification_displayed () {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_accept_displayed");
|
||||
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
do_check_eq(listener.notifyUserCount, 0);
|
||||
do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0);
|
||||
|
||||
// Uploads will trigger user notifications as needed.
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.notifyUserCount, 1);
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
|
||||
do_check_eq(listener.notifyUserCount, 1);
|
||||
do_check_true(policy.dataSubmissionPolicyNotifiedDate.getTime() > 0);
|
||||
do_check_true(policy.userNotifiedOfCurrentPolicy);
|
||||
});
|
||||
|
||||
add_task(function* test_submission_kill_switch() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_kill_switch");
|
||||
policy.nextDataSubmissionDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
|
||||
defineNow(policy,
|
||||
new Date(Date.now() + policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC + 100));
|
||||
policy.dataSubmissionEnabled = false;
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
});
|
||||
|
||||
add_task(function* test_upload_kill_switch() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("upload_kill_switch");
|
||||
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
|
||||
// So that we don't trigger deletions, which cause uploads to be delayed.
|
||||
hrPrefs.ignore("uploadEnabled", policy.uploadEnabledObserver);
|
||||
|
||||
policy.healthReportUploadEnabled = false;
|
||||
yield policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
policy.healthReportUploadEnabled = true;
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
});
|
||||
|
||||
add_task(function* test_data_submission_no_data() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_no_data");
|
||||
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime() + 1);
|
||||
defineNow(policy, now);
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
listener.lastDataRequest.onNoDataAvailable();
|
||||
|
||||
// The next trigger should try again.
|
||||
defineNow(policy, new Date(now.getTime() + 155 * 60 * 1000));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 2);
|
||||
});
|
||||
|
||||
add_task(function* test_data_submission_submit_failure_hard() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_submit_failure_hard");
|
||||
|
||||
let nextDataSubmissionDate = policy.nextDataSubmissionDate;
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime() + 1);
|
||||
defineNow(policy, now);
|
||||
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
yield listener.lastDataRequest.onSubmissionFailureHard();
|
||||
do_check_eq(listener.lastDataRequest.state,
|
||||
listener.lastDataRequest.SUBMISSION_FAILURE_HARD);
|
||||
|
||||
let expected = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(), expected.getTime());
|
||||
|
||||
defineNow(policy, new Date(now.getTime() + 10));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
});
|
||||
|
||||
add_task(function* test_data_submission_submit_try_again() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_failure_soft");
|
||||
|
||||
let nextDataSubmissionDate = policy.nextDataSubmissionDate;
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
yield listener.lastDataRequest.onSubmissionFailureSoft();
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
nextDataSubmissionDate.getTime() + 15 * 60 * 1000);
|
||||
});
|
||||
|
||||
add_task(function* test_submission_daily_scheduling() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_daily_scheduling");
|
||||
|
||||
let nextDataSubmissionDate = policy.nextDataSubmissionDate;
|
||||
|
||||
// Skip ahead to next submission date. We should get a submission request.
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), now.getTime());
|
||||
|
||||
let finishedDate = new Date(now.getTime() + 250);
|
||||
defineNow(policy, new Date(finishedDate.getTime() + 50));
|
||||
yield listener.lastDataRequest.onSubmissionSuccess(finishedDate);
|
||||
do_check_eq(policy.lastDataSubmissionSuccessfulDate.getTime(), finishedDate.getTime());
|
||||
|
||||
// Next scheduled submission should be exactly 1 day after the reported
|
||||
// submission success.
|
||||
|
||||
let nextScheduled = new Date(finishedDate.getTime() + 24 * 60 * 60 * 1000);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(), nextScheduled.getTime());
|
||||
|
||||
// Fast forward some arbitrary time. We shouldn't do any work yet.
|
||||
defineNow(policy, new Date(now.getTime() + 40000));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
|
||||
defineNow(policy, nextScheduled);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 2);
|
||||
yield listener.lastDataRequest.onSubmissionSuccess(new Date(nextScheduled.getTime() + 200));
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
new Date(nextScheduled.getTime() + 24 * 60 * 60 * 1000 + 200).getTime());
|
||||
});
|
||||
|
||||
add_task(function* test_submission_far_future_scheduling() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_far_future_scheduling");
|
||||
|
||||
let now = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
defineNow(policy, now);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
|
||||
let nextDate = policy._futureDate(3 * 24 * 60 * 60 * 1000 - 1);
|
||||
policy.nextDataSubmissionDate = nextDate;
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_true(policy.dataSubmissionPolicyAcceptedVersion >= DATAREPORTING_POLICY_VERSION);
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(), nextDate.getTime());
|
||||
|
||||
policy.nextDataSubmissionDate = new Date(nextDate.getTime() + 1);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
policy._futureDate(24 * 60 * 60 * 1000).getTime());
|
||||
});
|
||||
|
||||
add_task(function* test_submission_backoff() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_backoff");
|
||||
|
||||
do_check_eq(policy.FAILURE_BACKOFF_INTERVALS.length, 2);
|
||||
|
||||
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
do_check_eq(policy.currentDaySubmissionFailureCount, 0);
|
||||
|
||||
now = new Date(now.getTime() + 5000);
|
||||
defineNow(policy, now);
|
||||
|
||||
// On first soft failure we should back off by scheduled interval.
|
||||
yield listener.lastDataRequest.onSubmissionFailureSoft();
|
||||
do_check_eq(policy.currentDaySubmissionFailureCount, 1);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
new Date(now.getTime() + policy.FAILURE_BACKOFF_INTERVALS[0]).getTime());
|
||||
do_check_eq(policy.lastDataSubmissionFailureDate.getTime(), now.getTime());
|
||||
|
||||
// Should not request submission until scheduled.
|
||||
now = new Date(policy.nextDataSubmissionDate.getTime() - 1);
|
||||
defineNow(policy, now);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
|
||||
// 2nd request for submission.
|
||||
now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 2);
|
||||
|
||||
now = new Date(now.getTime() + 5000);
|
||||
defineNow(policy, now);
|
||||
|
||||
// On second failure we should back off by more.
|
||||
yield listener.lastDataRequest.onSubmissionFailureSoft();
|
||||
do_check_eq(policy.currentDaySubmissionFailureCount, 2);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
new Date(now.getTime() + policy.FAILURE_BACKOFF_INTERVALS[1]).getTime());
|
||||
|
||||
now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 3);
|
||||
|
||||
now = new Date(now.getTime() + 5000);
|
||||
defineNow(policy, now);
|
||||
|
||||
// On 3rd failure we should back off by a whole day.
|
||||
yield listener.lastDataRequest.onSubmissionFailureSoft();
|
||||
do_check_eq(policy.currentDaySubmissionFailureCount, 0);
|
||||
do_check_eq(policy.nextDataSubmissionDate.getTime(),
|
||||
new Date(now.getTime() + 24 * 60 * 60 * 1000).getTime());
|
||||
});
|
||||
|
||||
// Ensure that only one submission request can be active at a time.
|
||||
add_task(function* test_submission_expiring() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_expiring");
|
||||
|
||||
let nextDataSubmission = policy.nextDataSubmissionDate;
|
||||
let now = new Date(policy.nextDataSubmissionDate.getTime());
|
||||
defineNow(policy, now);
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
defineNow(policy, new Date(now.getTime() + 500));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
|
||||
defineNow(policy, new Date(policy.now().getTime() +
|
||||
policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC));
|
||||
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 2);
|
||||
});
|
||||
|
||||
add_task(function* test_delete_remote_data() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data");
|
||||
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
let nextSubmissionDate = policy.nextDataSubmissionDate;
|
||||
|
||||
let now = new Date();
|
||||
defineNow(policy, now);
|
||||
|
||||
policy.deleteRemoteData();
|
||||
do_check_true(policy.pendingDeleteRemoteData);
|
||||
do_check_neq(nextSubmissionDate.getTime(),
|
||||
policy.nextDataSubmissionDate.getTime());
|
||||
do_check_eq(now.getTime(), policy.nextDataSubmissionDate.getTime());
|
||||
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
do_check_true(listener.lastRemoteDeleteRequest.isDelete);
|
||||
defineNow(policy, policy._futureDate(1000));
|
||||
|
||||
yield listener.lastRemoteDeleteRequest.onSubmissionSuccess(policy.now());
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
});
|
||||
|
||||
// Ensure that deletion requests take priority over regular data submission.
|
||||
add_task(function* test_delete_remote_data_priority() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_priority");
|
||||
|
||||
let now = new Date();
|
||||
defineNow(policy, new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000));
|
||||
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
policy._inProgressSubmissionRequest = null;
|
||||
|
||||
policy.deleteRemoteData();
|
||||
policy.checkStateAndTrigger();
|
||||
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
});
|
||||
|
||||
add_test(function test_delete_remote_data_backoff() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_backoff");
|
||||
|
||||
let now = new Date();
|
||||
defineNow(policy, now);
|
||||
policy.nextDataSubmissionDate = now;
|
||||
policy.deleteRemoteData();
|
||||
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
defineNow(policy, policy._futureDate(1000));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
|
||||
defineNow(policy, policy._futureDate(500));
|
||||
listener.lastRemoteDeleteRequest.onSubmissionFailureSoft();
|
||||
defineNow(policy, policy._futureDate(50));
|
||||
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
|
||||
defineNow(policy, policy._futureDate(policy.FAILURE_BACKOFF_INTERVALS[0] - 50));
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 2);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// If we request delete while an upload is in progress, delete should be
|
||||
// scheduled immediately after upload.
|
||||
add_task(function* test_delete_remote_data_in_progress_upload() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_in_progress_upload");
|
||||
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
|
||||
yield ensureUserNotifiedAndTrigger(policy);
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
defineNow(policy, policy._futureDate(50 * 1000));
|
||||
|
||||
// If we request a delete during a pending request, nothing should be done.
|
||||
policy.deleteRemoteData();
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 0);
|
||||
|
||||
// Now wait a little bit and finish the request.
|
||||
defineNow(policy, policy._futureDate(10 * 1000));
|
||||
yield listener.lastDataRequest.onSubmissionSuccess(policy._futureDate(1000));
|
||||
defineNow(policy, policy._futureDate(5000));
|
||||
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.requestDataUploadCount, 1);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
});
|
||||
|
||||
add_test(function test_polling() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("polling");
|
||||
let intended = 500;
|
||||
let acceptable = 250; // Because nsITimer doesn't guarantee times.
|
||||
|
||||
// Ensure checkStateAndTrigger is called at a regular interval.
|
||||
let then = Date.now();
|
||||
print("Starting run: " + then);
|
||||
Object.defineProperty(policy, "POLL_INTERVAL_MSEC", {
|
||||
value: intended,
|
||||
});
|
||||
let count = 0;
|
||||
|
||||
Object.defineProperty(policy, "checkStateAndTrigger", {
|
||||
value: function fakeCheckStateAndTrigger() {
|
||||
let now = Date.now();
|
||||
let after = now - then;
|
||||
count++;
|
||||
|
||||
print("Polled at " + now + " after " + after + "ms, intended " + intended);
|
||||
do_check_true(after >= acceptable);
|
||||
DataReportingPolicy.prototype.checkStateAndTrigger.call(policy);
|
||||
|
||||
if (count >= 2) {
|
||||
policy.stopPolling();
|
||||
|
||||
do_check_eq(listener.requestDataUploadCount, 0);
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// "Specified timer period will be at least the time between when
|
||||
// processing for last firing the callback completes and when the next
|
||||
// firing occurs."
|
||||
//
|
||||
// That means we should set 'then' at the *end* of our handler, not
|
||||
// earlier.
|
||||
then = Date.now();
|
||||
}
|
||||
});
|
||||
policy.startPolling();
|
||||
});
|
||||
|
||||
add_task(function* test_record_health_report_upload_enabled() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("record_health_report_upload_enabled");
|
||||
|
||||
// Preconditions.
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
do_check_true(policy.healthReportUploadEnabled);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 0);
|
||||
|
||||
// User intent to disable should immediately result in a pending
|
||||
// delete request.
|
||||
policy.recordHealthReportUploadEnabled(false, "testing 1 2 3");
|
||||
do_check_false(policy.healthReportUploadEnabled);
|
||||
do_check_true(policy.pendingDeleteRemoteData);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 1);
|
||||
|
||||
// Fulfilling it should make it go away.
|
||||
yield listener.lastRemoteDeleteRequest.onNoDataAvailable();
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
|
||||
// User intent to enable should get us back to default state.
|
||||
policy.recordHealthReportUploadEnabled(true, "testing 1 2 3");
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
do_check_true(policy.healthReportUploadEnabled);
|
||||
});
|
||||
|
||||
add_test(function test_pref_change_initiates_deletion() {
|
||||
let [policy, policyPrefs, hrPrefs, listener] = getPolicy("record_health_report_upload_enabled");
|
||||
|
||||
// Preconditions.
|
||||
do_check_false(policy.pendingDeleteRemoteData);
|
||||
do_check_true(policy.healthReportUploadEnabled);
|
||||
do_check_eq(listener.requestRemoteDeleteCount, 0);
|
||||
|
||||
// User intent to disable should indirectly result in a pending
|
||||
// delete request, because the policy is watching for the pref
|
||||
// to change.
|
||||
Object.defineProperty(policy, "deleteRemoteData", {
|
||||
value: function deleteRemoteDataProxy() {
|
||||
do_check_false(policy.healthReportUploadEnabled);
|
||||
do_check_false(policy.pendingDeleteRemoteData); // Just called.
|
||||
|
||||
run_next_test();
|
||||
},
|
||||
});
|
||||
|
||||
hrPrefs.set("uploadEnabled", false);
|
||||
});
|
||||
|
||||
add_task(function* test_policy_version() {
|
||||
let policy, policyPrefs, hrPrefs, listener, now, firstRunTime;
|
||||
function createPolicy(shouldBeNotified = false,
|
||||
currentPolicyVersion = 1, minimumPolicyVersion = 1,
|
||||
branchMinimumVersionOverride) {
|
||||
[policy, policyPrefs, hrPrefs, listener] =
|
||||
getPolicy("policy_version_test", currentPolicyVersion,
|
||||
minimumPolicyVersion, branchMinimumVersionOverride);
|
||||
let firstRun = now === undefined;
|
||||
if (firstRun) {
|
||||
firstRunTime = policy.firstRunDate.getTime();
|
||||
do_check_true(firstRunTime > 0);
|
||||
now = new Date(policy.firstRunDate.getTime());
|
||||
}
|
||||
else {
|
||||
// The first-run time should not be reset even after policy-version
|
||||
// upgrades.
|
||||
do_check_eq(policy.firstRunDate.getTime(), firstRunTime);
|
||||
}
|
||||
defineNow(policy, now);
|
||||
do_check_eq(policy.userNotifiedOfCurrentPolicy, shouldBeNotified);
|
||||
}
|
||||
|
||||
function* triggerPolicyCheckAndEnsureNotified(notified = true) {
|
||||
policy.checkStateAndTrigger();
|
||||
do_check_eq(listener.notifyUserCount, Number(notified));
|
||||
if (notified) {
|
||||
policy.ensureUserNotified();
|
||||
yield listener.lastNotifyRequest.deferred.promise;
|
||||
do_check_true(policy.userNotifiedOfCurrentPolicy);
|
||||
do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"),
|
||||
policyPrefs.get("currentPolicyVersion"));
|
||||
}
|
||||
}
|
||||
|
||||
createPolicy();
|
||||
yield triggerPolicyCheckAndEnsureNotified();
|
||||
|
||||
// We shouldn't be notified again if the current version is still valid;
|
||||
createPolicy(true);
|
||||
yield triggerPolicyCheckAndEnsureNotified(false);
|
||||
|
||||
// Just increasing the current version isn't enough. The minimum
|
||||
// version must be changed.
|
||||
let currentPolicyVersion = policyPrefs.get("currentPolicyVersion");
|
||||
let minimumPolicyVersion = policyPrefs.get("minimumPolicyVersion");
|
||||
createPolicy(false, ++currentPolicyVersion, minimumPolicyVersion);
|
||||
yield triggerPolicyCheckAndEnsureNotified(true);
|
||||
do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), currentPolicyVersion);
|
||||
|
||||
// Increase the minimum policy version and check if we're notified.
|
||||
|
||||
createPolicy(true, currentPolicyVersion, ++minimumPolicyVersion);
|
||||
do_check_true(policyPrefs.has("dataSubmissionPolicyAcceptedVersion"));
|
||||
yield triggerPolicyCheckAndEnsureNotified(false);
|
||||
|
||||
|
||||
// Test increasing the minimum version just on the current channel.
|
||||
createPolicy(true, currentPolicyVersion, minimumPolicyVersion);
|
||||
yield triggerPolicyCheckAndEnsureNotified(false);
|
||||
createPolicy(false, ++currentPolicyVersion, minimumPolicyVersion, minimumPolicyVersion + 1);
|
||||
yield triggerPolicyCheckAndEnsureNotified(true);
|
||||
});
|
@ -1,6 +0,0 @@
|
||||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_policy.js]
|
@ -1,28 +0,0 @@
|
||||
.. _data_reporting_service:
|
||||
|
||||
======================
|
||||
Data Reporting Service
|
||||
======================
|
||||
|
||||
``/services/datareporting`` contains files related to an XPCOM service
|
||||
that collects and reports data within Gecko applications.
|
||||
|
||||
The important files in this directory are:
|
||||
|
||||
DataReportingService.js
|
||||
An XPCOM service that coordinates collection and reporting of data.
|
||||
|
||||
policy.jsm
|
||||
A module containing the logic for coordinating and driving collection
|
||||
and upload of data.
|
||||
|
||||
sessions.jsm
|
||||
Records Gecko application session history. This is loaded as part of
|
||||
the XPCOM service because it needs to capture state from very early in
|
||||
the application lifecycle. Bug 841561 tracks implementing this in C++.
|
||||
|
||||
There is other code in the tree that collects and uploads data. The
|
||||
original intent of this directory and XPCOM service was to serve as a
|
||||
focal point for the coordination of all this activity so that it could
|
||||
all be done consistently and properly. This vision may or may not be fully
|
||||
realized.
|
@ -14,4 +14,3 @@ the directory remains.
|
||||
:maxdepth: 1
|
||||
|
||||
metrics
|
||||
datareporting
|
||||
|
@ -18,9 +18,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
|
||||
if CONFIG['MOZ_SERVICES_HEALTHREPORT']:
|
||||
DIRS += ['healthreport']
|
||||
|
||||
if CONFIG['MOZ_DATA_REPORTING']:
|
||||
DIRS += ['datareporting']
|
||||
|
||||
if CONFIG['MOZ_SERVICES_METRICS']:
|
||||
DIRS += ['metrics']
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pref("datareporting.policy.dataSubmissionEnabled", true);
|
||||
pref("datareporting.policy.dataSubmissionEnabled.v2", false);
|
||||
pref("datareporting.policy.firstRunTime", "0");
|
||||
pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "0");
|
||||
pref("datareporting.policy.dataSubmissionPolicyAcceptedVersion", 0);
|
||||
pref("datareporting.policy.dataSubmissionPolicyBypassNotification", false);
|
@ -22,6 +22,9 @@ const STARTUP_RETRY_INTERVAL_MS = 5000;
|
||||
// Wait up to 5 minutes for startup measurements before giving up.
|
||||
const MAX_STARTUP_TRIES = 300000 / STARTUP_RETRY_INTERVAL_MS;
|
||||
|
||||
const LOGGER_NAME = "Toolkit.Telemetry";
|
||||
const LOGGER_PREFIX = "SessionRecorder::";
|
||||
|
||||
/**
|
||||
* Records information about browser sessions.
|
||||
*
|
||||
@ -64,7 +67,7 @@ this.SessionRecorder = function (branch) {
|
||||
throw new Error("branch argument must end with '.': " + branch);
|
||||
}
|
||||
|
||||
this._log = Log.repository.getLogger("Services.DataReporting.SessionRecorder");
|
||||
this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
|
||||
|
||||
this._prefs = new Preferences(branch);
|
||||
this._lastActivityWasInactive = false;
|
||||
|
Loading…
Reference in New Issue
Block a user