Bug 1234522 - Remove services/datareporting. r=gfritzsche

This commit is contained in:
Alessio Placitelli 2016-01-05 02:01:00 +01:00
parent bc2c8cb929
commit 441e819b1d
16 changed files with 9 additions and 2066 deletions

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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
;

View File

@ -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();
}
},
};

View File

@ -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',
]

View File

@ -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);
},
});

View File

@ -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);

View File

@ -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);
});

View File

@ -1,6 +0,0 @@
[DEFAULT]
head = head.js
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_policy.js]

View File

@ -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.

View File

@ -14,4 +14,3 @@ the directory remains.
:maxdepth: 1
metrics
datareporting

View File

@ -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']

View File

@ -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);

View File

@ -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;