Remove telemetry experiments framework

This commit is contained in:
wolfbeast
2018-04-08 12:24:27 +02:00
parent 309d1ca1f2
commit bf6bb142fc
38 changed files with 2 additions and 8345 deletions
-7
View File
@@ -1392,13 +1392,6 @@ pref("browser.translation.engine", "bing");
// Determines if Telemetry pings can be archived locally.
pref("toolkit.telemetry.archive.enabled", true);
// Telemetry experiments settings.
pref("experiments.enabled", true);
pref("experiments.manifest.fetchIntervalSeconds", 86400);
pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
// Whether experiments are supported by the current application profile.
pref("experiments.supported", true);
// Enable GMP support in the addon manager.
pref("media.gmp-provider.enabled", true);
-11
View File
@@ -1,11 +0,0 @@
"use strict";
module.exports = {
"rules": {
"no-unused-vars": ["error", {
"vars": "all",
"varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
"args": "none"
}]
}
};
File diff suppressed because it is too large Load Diff
-6
View File
@@ -1,6 +0,0 @@
component {f7800463-3b97-47f9-9341-b7617e6d8d49} ExperimentsService.js
contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b7617e6d8d49}
category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400
category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1
-118
View File
@@ -1,118 +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 {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
"resource://services-common/utils.js");
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
const PREF_ACTIVE_EXPERIMENT = "experiments.activeExperiment"; // whether we have an active experiment
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
const DELAY_INIT_MS = 30 * 1000;
// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_TELEMETRY_UNIFIED, false);
XPCOMUtils.defineLazyGetter(
this, "gPrefs", () => {
return new Preferences();
});
XPCOMUtils.defineLazyGetter(
this, "gExperimentsEnabled", () => {
// We can enable experiments if either unified Telemetry or FHR is on, and the user
// has opted into Telemetry.
return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) &&
IS_UNIFIED_TELEMETRY && gPrefs.get(PREF_TELEMETRY_ENABLED, false);
});
XPCOMUtils.defineLazyGetter(
this, "gActiveExperiment", () => {
return gPrefs.get(PREF_ACTIVE_EXPERIMENT);
});
function ExperimentsService() {
this._initialized = false;
this._delayedInitTimer = null;
}
ExperimentsService.prototype = {
classID: Components.ID("{f7800463-3b97-47f9-9341-b7617e6d8d49}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
notify: function (timer) {
if (!gExperimentsEnabled) {
return;
}
if (OS.Constants.Path.profileDir === undefined) {
throw Error("Update timer fired before profile was initialized?");
}
let instance = Experiments.instance();
if (instance.isReady) {
instance.updateManifest();
}
},
_delayedInit: function () {
if (!this._initialized) {
this._initialized = true;
Experiments.instance(); // for side effects
}
},
observe: function (subject, topic, data) {
switch (topic) {
case "profile-after-change":
if (gExperimentsEnabled) {
Services.obs.addObserver(this, "quit-application", false);
Services.obs.addObserver(this, "sessionstore-state-finalized", false);
Services.obs.addObserver(this, "EM-loaded", false);
if (gActiveExperiment) {
this._initialized = true;
Experiments.instance(); // for side effects
}
}
break;
case "sessionstore-state-finalized":
if (!this._initialized) {
CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
}
break;
case "EM-loaded":
if (!this._initialized) {
Experiments.instance(); // for side effects
this._initialized = true;
if (this._delayedInitTimer) {
this._delayedInitTimer.clear();
}
}
break;
case "quit-application":
Services.obs.removeObserver(this, "quit-application");
Services.obs.removeObserver(this, "sessionstore-state-finalized");
Services.obs.removeObserver(this, "EM-loaded");
if (this._delayedInitTimer) {
this._delayedInitTimer.clear();
}
break;
}
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExperimentsService]);
-16
View File
@@ -1,16 +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/.
include $(topsrcdir)/config/rules.mk
# This is so hacky. Waiting on bug 988938.
addondir = $(srcdir)/test/addons
testdir = $(topobjdir)/_tests/xpcshell/browser/experiments/test/xpcshell
misc:: $(call mkdir_deps,$(testdir))
$(EXIT_ON_ERROR) \
for dir in $(addondir)/*; do \
base=`basename $$dir`; \
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
done
-13
View File
@@ -1,13 +0,0 @@
=====================
Telemetry Experiments
=====================
Telemetry Experiments is a feature of Firefox that allows the installation
of add-ons called experiments to a subset of the Firefox population for
the purposes of experimenting with changes and collecting data on specific
aspects of application usage.
.. toctree::
:maxdepth: 1
manifest
-429
View File
@@ -1,429 +0,0 @@
.. _experiments_manifests:
=====================
Experiments Manifests
=====================
*Experiments Manifests* are documents that describe the set of active
experiments a client may run.
*Experiments Manifests* are fetched periodically by clients. When
fetched, clients look at the experiments within the manifest and
determine which experiments are applicable. If an experiment is
applicable, the client may download and start the experiment.
Manifest Format
===============
Manifests are JSON documents where the main element is an object.
The *schema* of the object is versioned and defined by the presence
of a top-level ``version`` property, whose integer value is the
schema version used by that manifest. Each version is documented
in the sections below.
Version 1
---------
Version 1 is the original manifest format.
The following properties may exist in the root object:
experiments
An array of objects describing candidate experiments. The format of
these objects is documented below.
An array is used to create an explicit priority of experiments.
Experiments listed at the beginning of the array take priority over
experiments that follow.
Experiments Objects
^^^^^^^^^^^^^^^^^^^
Each object in the ``experiments`` array may contain the following
properties:
id
(required) String identifier of this experiment. The identifier should
be treated as opaque by clients. It is used to uniquely identify an
experiment for all of time.
xpiURL
(required) String URL of the XPI that implements this experiment.
If the experiment is activated, the client will download and install this
XPI.
xpiHash
(required) String hash of the XPI that implements this experiment.
The value is composed of a hash identifier followed by a colon
followed by the hash value. e.g.
`sha1:f677428b9172e22e9911039aef03f3736e7f78a7`. `sha1` and `sha256`
are the two supported hashing mechanisms. The hash value is the hex
encoding of the binary hash.
When the client downloads the XPI for the experiment, it should compare
the hash of that XPI against this value. If the hashes don't match,
the client should not install the XPI.
Clients may also use this hash as a means of determining when an
experiment's XPI has changed and should be refreshed.
startTime
Integer seconds since UNIX epoch that this experiment should
start. Clients should not start an experiment if *now()* is less than
this value.
maxStartTime
(optional) Integer seconds since UNIX epoch after which this experiment
should no longer start.
Some experiments may wish to impose hard deadlines after which no new
clients should activate the experiment. This property may be used to
facilitate that.
endTime
Integer seconds since UNIX epoch after which this experiment
should no longer run. Clients should cease an experiment when the current
time is beyond this value.
maxActiveSeconds
Integer seconds defining the max wall time this experiment should be
active for.
The client should deactivate the experiment this many seconds after
initial activation.
This value only involves wall time, not browser activity or session time.
appName
Array of application names this experiment should run on.
An application name comes from ``nsIXULAppInfo.name``. It is a value
like ``Firefox``, ``Fennec``, or `B2G`.
The client should compare its application name against the members of
this array. If a match is found, the experiment is applicable.
minVersion
(optional) String version number of the minimum application version this
experiment should run on.
A version number is something like ``27.0.0`` or ``28``.
The client should compare its version number to this value. If the client's
version is greater or equal to this version (using a version-aware comparison
function), the experiment is applicable.
If this is not specified, there is no lower bound to versions this
experiment should run on.
maxVersion
(optional) String version number of the maximum application version this
experiment should run on.
This is similar to ``minVersion`` except it sets the upper bound for
application versions.
If the client's version is less than or equal to this version, the
experiment is applicable.
If this is not specified, there is no upper bound to versions this
experiment should run on.
version
(optional) Array of application versions this experiment should run on.
This is similar to ``minVersion`` and ``maxVersion`` except only a
whitelisted set of specific versions are allowed.
The client should compare its version to members of this array. If a match
is found, the experiment is applicable.
minBuildID
(optional) String minimum Build ID this experiment should run on.
Build IDs are values like ``201402261424``.
The client should perform a string comparison of its Build ID against this
value. If its value is greater than or equal to this value, the experiment
is applicable.
maxBuildID
(optional) String maximum Build ID this experiment should run on.
This is similar to ``minBuildID`` except it sets the upper bound
for Build IDs.
The client should perform a string comparison of its Build ID against
this value. If its value is less than or equal to this value, the
experiment is applicable.
buildIDs
(optional) Array of Build IDs this experiment should run on.
This is similar to ``minBuildID`` and ``maxBuildID`` except only a
whitelisted set of Build IDs are considered.
The client should compare its Build ID to members of this array. If a
match is found, the experiment is applicable.
os
(optional) Array of operating system identifiers this experiment should
run on.
Values for this array come from ``nsIXULRuntime.OS``.
The client will compare its operating system identifier to members
of this array. If a match is found, the experiment is applicable to the
client.
channel
(optional) Array of release channel identifiers this experiment should run
on.
The client will compare its channel to members of this array. If a match
is found, the experiment is applicable.
If this property is not defined, the client should assume the experiment
is to run on all channels.
locale
(optional) Array of locale identifiers this experiment should run on.
A locale identifier is a string like ``en-US`` or ``zh-CN`` and is
obtained by looking at
``nsIXULChromeRegistry.getSelectedLocale("global")``.
The client should compare its locale identifier to members of this array.
If a match is found, the experiment is applicable.
If this property is not defined, the client should assume the experiment
is to run on all locales.
sample
(optional) Decimal number indicating the sampling rate for this experiment.
This will contain a value between ``0.0`` and ``1.0``. The client should
generate a random decimal between ``0.0`` and ``1.0``. If the randomly
generated number is less than or equal to the value of this field, the
experiment is applicable.
disabled
(optional) Boolean value indicating whether an experiment is disabled.
Normally, experiments are deactivated after a certain time has passed or
after the experiment itself determines it no longer needs to run (perhaps
it collected sufficient data already).
This property serves as a backup mechanism to remotely disable an
experiment before it was scheduled to be disabled. It can be used to
kill experiments that are found to be doing wrong or bad things or that
aren't useful.
If this property is not defined or is false, the client should assume
the experiment is active and a candidate for activation.
frozen
(optional) Boolean value indicating this experiment is frozen and no
longer accepting new enrollments.
If a client sees a true value in this field, it should not attempt to
activate an experiment.
jsfilter
(optional) JavaScript code that will be evaluated to determine experiment
applicability.
This property contains the string representation of JavaScript code that
will be evaluated in a sandboxed environment using JavaScript's
``eval()``.
The string is expected to contain the definition of a JavaScript function
``filter(context)``. This function receives as its argument an object
holding application state. See the section below for the definition of
this object.
The purpose of this property is to allow experiments to define complex
rules and logic for evaluating experiment applicability in a manner
that is privacy conscious and doesn't require the transmission of
excessive data.
The return value of this filter indicates whether the experiment is
applicable. Functions should return true if the experiment is
applicable.
If an experiment is not applicable, they should throw an Error whose
message contains the reason the experiment is not applicable. This
message may be logged and sent to remote servers, so it should not
contain private or otherwise sensitive data that wouldn't normally
be submitted.
If a falsey (or undefined) value is returned, the client should
assume the experiment is not applicable.
If this property is not defined, the client does not consider a custom
JavaScript filter function when determining whether an experiment is
applicable.
JavaScript Filter Context Objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The object passed to a ``jsfilter`` ``filter()`` function contains the
following properties:
healthReportSubmissionEnabled
This property contains a boolean indicating whether Firefox Health
Report has its data submission flag enabled (whether Firefox Health
Report is sending data to remote servers).
healthReportPayload
This property contains the current Firefox Health Report payload.
The payload format is documented at :ref:`healthreport_dataformat`.
telemetryPayload
This property contains the current Telemetry payload.
The evaluation sandbox for the JavaScript filters may be destroyed
immediately after ``filter()`` returns. This function should not assume
async code will finish.
Experiment Applicability and Client Behavior
============================================
The point of an experiment manifest is to define which experiments are
available and where and how to run them. This section explains those
rules in more detail.
Many of the properties in *Experiment Objects* are related to determining
whether an experiment should run on a given client. This evaluation is
performed client side.
1. Multiple conditions in an experiment
---------------------------------------
If multiple conditions are defined for an experiment, the client should
combine each condition with a logical *AND*: all conditions must be
satisfied for an experiment to run. If one condition fails, the experiment
is not applicable.
2. Active experiment disappears from manifest
---------------------------------------------
If a specific experiment disappears from the manifest, the client should
continue conducting an already-active experiment. Furthermore, the
client should remember what the expiration events were for an experiment
and honor them.
The rationale here is that we want to prevent an accidental deletion
or temporary failure on the server to inadvertantly deactivate
supposed-to-be-active experiments. We also don't want premature deletion
of an experiment from the manifest to result in indefinite activation
periods.
3. Inactive experiment disappears from manifest
-----------------------------------------------
If an inactive but scheduled-to-be-active experiment disappears from the
manifest, the client should not activate the experiment.
If that experiment reappears in the manifest, the client should not
treat that experiment any differently than any other new experiment. Put
another way, the fact an inactive experiment disappears and then
reappears should not be significant.
The rationale here is that server operators should have complete
control of an inactive experiment up to it's go-live date.
4. Re-evaluating applicability on manifest refresh
--------------------------------------------------
When an experiment manifest is refreshed or updated, the client should
re-evaluate the applicability of each experiment therein.
The rationale here is that the server may change the parameters of an
experiment and want clients to pick those up.
5. Activating a previously non-applicable experiment
----------------------------------------------------
If the conditions of an experiment change or the state of the client
changes to allow an experiment to transition from previously
non-applicable to applicable, the experiment should be activated.
For example, if a client is running version 28 and the experiment
initially requires version 29 or above, the client will not mark the
experiment as applicable. But if the client upgrades to version 29 or if
the manifest is updated to require 28 or above, the experiment will
become applicable.
6. Deactivating a previously active experiment
----------------------------------------------
If the conditions of an experiment change or the state of the client
changes and an active experiment is no longer applicable, that
experiment should be deactivated.
7. Calculation of sampling-based applicability
----------------------------------------------
For calculating sampling-based applicability, the client will associate
a random value between ``0.0`` and ``1.0`` for each observed experiment
ID. This random value will be generated the first time sampling
applicability is evaluated. This random value will be persisted and used
in future applicability evaluations for this experiment.
By saving and re-using the value, the client is able to reliably and
consistently evaluate applicability, even if the sampling threshold
in the manifest changes.
Clients should retain the randomly-generated sampling value for
experiments that no longer appear in a manifest for a period of at least
30 days. The rationale is that if an experiment disappears and reappears
from a manifest, the client will not have multiple opportunities to
generate a random value that satisfies the sampling criteria.
8. Incompatible version numbers
-------------------------------
If a client receives a manifest with a version number that it doesn't
recognize, it should ignore the manifest.
9. Usage of old manifests
-------------------------
If a client experiences an error fetching a manifest (server not
available) or if the manifest is corrupt, not readable, or compatible,
the client may use a previously-fetched (cached) manifest.
10. Updating XPIs
-----------------
If the URL or hash of an active experiment's XPI changes, the client
should fetch the new XPI, uninstall the old XPI, and install the new
XPI.
Examples
========
Here is an example manifest::
{
"version": 1,
"experiments": [
{
"id": "da9d7f4f-f3f9-4f81-bacd-6f0626ffa360",
"xpiURL": "https://experiments.mozilla.org/foo.xpi",
"xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
"startTime": 1393000000,
"endTime": 1394000000,
"appName": ["Firefox", "Fennec"],
"minVersion": "28",
"maxVersion": "30",
"os": ["windows", "linux", "osx"],
"jsfilter": "function filter(context) { return context.healthReportEnabled; }"
}
]
}
-18
View File
@@ -1,18 +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/.
HAS_MISC_RULE = True
EXTRA_COMPONENTS += [
'Experiments.manifest',
'ExperimentsService.js',
]
EXTRA_JS_MODULES.experiments += [
'Experiments.jsm',
]
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
SPHINX_TREES['experiments'] = 'docs'
@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-1@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 1</em:name>
<em:description>Yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>
@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-1@tests.mozilla.org</em:id>
<em:version>1.1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 1.1</em:name>
<em:description>And yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>
@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-2@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment 2</em:name>
<em:description>And yet another experiment that experiments experimentally.</em:description>
</Description>
</RDF>
@@ -1,35 +0,0 @@
/* exported startup, shutdown, install, uninstall */
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource:///modules/experiments/Experiments.jsm");
var gStarted = false;
function startup(data, reasonCode) {
if (gStarted) {
return;
}
gStarted = true;
// delay realstartup to trigger the race condition
Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager)
.mainThread.dispatch(realstartup, 0);
}
function realstartup() {
let experiments = Experiments.instance();
let experiment = experiments._getActiveExperiment();
if (experiment.branch) {
Cu.reportError("Found pre-existing branch: " + experiment.branch);
return;
}
let branch = "racy-set";
experiments.setExperimentBranch(experiment.id, branch)
.then(null, Cu.reportError);
}
function shutdown() { }
function install() { }
function uninstall() { }
@@ -1,16 +0,0 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test-experiment-racybranch@tests.mozilla.org</em:id>
<em:version>1</em:version>
<em:type>128</em:type>
<!-- Front End MetaData -->
<em:name>Test experiment racybranch</em:name>
<em:description>An experiment that sets the experiment branch in a potentially racy way.</em:description>
</Description>
</RDF>
@@ -1,15 +0,0 @@
"use strict";
module.exports = {
"extends": [
"../../../../testing/xpcshell/xpcshell.eslintrc.js"
],
"rules": {
"no-unused-vars": ["error", {
"vars": "all",
"varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
"args": "none"
}]
}
};
@@ -1,19 +0,0 @@
{
"version": 1,
"experiments": [
{
"id": "test-experiment-1@tests.mozilla.org",
"xpiURL": "https://experiments.mozilla.org/foo.xpi",
"xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
"startTime": 1393000000,
"endTime": 1394000000,
"appName": ["Firefox", "Fennec"],
"minVersion": "28",
"maxVersion": "30",
"maxActiveSeconds": 60,
"os": ["windows", "linux", "osx"],
"channel": ["daily", "weekly", "nightly"],
"jsfilter": "function filter(context) { return true; }"
}
]
}
-199
View File
@@ -1,199 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* exported PREF_EXPERIMENTS_ENABLED, PREF_LOGGING_LEVEL, PREF_LOGGING_DUMP
PREF_MANIFEST_URI, PREF_FETCHINTERVAL, EXPERIMENT1_ID,
EXPERIMENT1_NAME, EXPERIMENT1_XPI_SHA1, EXPERIMENT1A_NAME,
EXPERIMENT1A_XPI_SHA1, EXPERIMENT2_ID, EXPERIMENT2_XPI_SHA1,
EXPERIMENT3_ID, EXPERIMENT4_ID, FAKE_EXPERIMENTS_1,
FAKE_EXPERIMENTS_2, gAppInfo, removeCacheFile, defineNow,
futureDate, dateToSeconds, loadAddonManager, promiseRestartManager,
startAddonManagerOnly, getExperimentAddons, replaceExperiments */
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
Cu.import("resource://testing-common/AddonTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
const PREF_LOGGING_LEVEL = "experiments.logging.level";
const PREF_LOGGING_DUMP = "experiments.logging.dump";
const PREF_MANIFEST_URI = "experiments.manifest.uri";
const PREF_FETCHINTERVAL = "experiments.manifest.fetchIntervalSeconds";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
function getExperimentPath(base) {
let p = do_get_cwd();
p.append(base);
return p.path;
}
function sha1File(path) {
let f = Cc["@mozilla.org/file/local;1"]
.createInstance(Ci.nsILocalFile);
f.initWithPath(path);
let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
hasher.init(hasher.SHA1);
let is = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
is.init(f, -1, 0, 0);
hasher.updateFromStream(is, Math.pow(2, 32) - 1);
is.close();
let bytes = hasher.finish(false);
let rv = "";
for (let i = 0; i < bytes.length; i++) {
rv += ("0" + bytes.charCodeAt(i).toString(16)).substr(-2);
}
return rv;
}
const EXPERIMENT1_ID = "test-experiment-1@tests.mozilla.org";
const EXPERIMENT1_XPI_NAME = "experiment-1.xpi";
const EXPERIMENT1_NAME = "Test experiment 1";
const EXPERIMENT1_PATH = getExperimentPath(EXPERIMENT1_XPI_NAME);
const EXPERIMENT1_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1_PATH);
const EXPERIMENT1A_XPI_NAME = "experiment-1a.xpi";
const EXPERIMENT1A_NAME = "Test experiment 1.1";
const EXPERIMENT1A_PATH = getExperimentPath(EXPERIMENT1A_XPI_NAME);
const EXPERIMENT1A_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1A_PATH);
const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
const EXPERIMENT2_PATH = getExperimentPath(EXPERIMENT2_XPI_NAME);
const EXPERIMENT2_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT2_PATH);
const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";
const FAKE_EXPERIMENTS_1 = [
{
id: "id1",
name: "experiment1",
description: "experiment 1",
active: true,
detailUrl: "https://dummy/experiment1",
branch: "foo",
},
];
const FAKE_EXPERIMENTS_2 = [
{
id: "id2",
name: "experiment2",
description: "experiment 2",
active: false,
endDate: new Date(2014, 2, 11, 2, 4, 35, 42).getTime(),
detailUrl: "https://dummy/experiment2",
branch: null,
},
{
id: "id1",
name: "experiment1",
description: "experiment 1",
active: false,
endDate: new Date(2014, 2, 10, 0, 0, 0, 0).getTime(),
detailURL: "https://dummy/experiment1",
branch: null,
},
];
var gAppInfo = null;
function removeCacheFile() {
let path = OS.Path.join(OS.Constants.Path.profileDir, "experiments.json");
return OS.File.remove(path);
}
function patchPolicy(policy, data) {
for (let key of Object.keys(data)) {
Object.defineProperty(policy, key, {
value: data[key],
writable: true,
});
}
}
function defineNow(policy, time) {
patchPolicy(policy, { now: () => new Date(time) });
}
function futureDate(date, offset) {
return new Date(date.getTime() + offset);
}
function dateToSeconds(date) {
return date.getTime() / 1000;
}
var gGlobalScope = this;
function loadAddonManager() {
AddonTestUtils.init(gGlobalScope);
AddonTestUtils.overrideCertDB();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
return AddonTestUtils.promiseStartupManager();
}
const {
promiseRestartManager,
} = AddonTestUtils;
// Starts the addon manager without creating app info. We can't directly use
// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
function startAddonManagerOnly() {
let addonManager = Cc["@mozilla.org/addons/integration;1"]
.getService(Ci.nsIObserver)
.QueryInterface(Ci.nsITimerCallback);
addonManager.observe(null, "addons-startup", null);
}
function getExperimentAddons(previous=false) {
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
if (previous) {
deferred.resolve(addons);
} else {
deferred.resolve(addons.filter(a => !a.appDisabled));
}
});
return deferred.promise;
}
function createAppInfo(ID="xpcshell@tests.mozilla.org", name="XPCShell",
version="1.0", platformVersion="1.0") {
AddonTestUtils.createAppInfo(ID, name, version, platformVersion);
gAppInfo = AddonTestUtils.appInfo;
}
/**
* Replace the experiments on an Experiments with a new list.
*
* This monkeypatches getExperiments(). It doesn't monkeypatch the internal
* experiments list. So its utility is not as great as it could be.
*/
function replaceExperiments(experiment, list) {
Object.defineProperty(experiment, "getExperiments", {
writable: true,
value: () => {
return Promise.resolve(list);
},
});
}
// Experiments require Telemetry to be enabled, and that's not true for debug
// builds. Let's just enable it here instead of going through each test.
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
@@ -1,151 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource:///modules/experiments/Experiments.jsm");
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
var gHttpServer = null;
var gHttpRoot = null;
var gPolicy = null;
function ManifestEntry(data) {
this.id = data.id || EXPERIMENT1_ID;
this.xpiURL = data.xpiURL || gHttpRoot + EXPERIMENT1_XPI_NAME;
this.xpiHash = data.xpiHash || EXPERIMENT1_XPI_SHA1;
this.appName = data.appName || ["XPCShell"];
this.channel = data.appName || ["nightly"];
this.startTime = data.startTime || new Date(2010, 0, 1, 12).getTime() / 1000;
this.endTime = data.endTime || new Date(9001, 0, 1, 12).getTime() / 1000;
this.maxActiveSeconds = data.maxActiveSeconds || 5 * SEC_IN_ONE_DAY;
}
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
gPolicy = new Experiments.Policy();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gHttpServer.registerDirectory("/", do_get_cwd());
do_register_cleanup(() => gHttpServer.stop(() => {}));
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
});
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
});
function isApplicable(experiment) {
let deferred = Promise.defer();
experiment.isApplicable().then(
result => deferred.resolve({ applicable: true, reason: null }),
reason => deferred.resolve({ applicable: false, reason: reason })
);
return deferred.promise;
}
add_task(function* test_startStop() {
let baseDate = new Date(2014, 5, 1, 12);
let startDate = futureDate(baseDate, 30 * MS_IN_ONE_DAY);
let endDate = futureDate(baseDate, 60 * MS_IN_ONE_DAY);
let manifestData = new ManifestEntry({
startTime: dateToSeconds(startDate),
endTime: dateToSeconds(endDate),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
});
let experiment = new Experiments.ExperimentEntry(gPolicy);
experiment.initFromManifestData(manifestData);
// We need to associate it with the singleton so the onInstallStarted
// Addon Manager listener will know about it.
Experiments.instance()._experiments = new Map();
Experiments.instance()._experiments.set(experiment.id, experiment);
let result;
defineNow(gPolicy, baseDate);
result = yield isApplicable(experiment);
Assert.equal(result.applicable, false, "Experiment should not be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
result = yield isApplicable(experiment);
Assert.equal(result.applicable, true, "Experiment should now be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
let changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
changes = yield experiment.stop();
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
result = yield experiment.shouldStop();
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
Assert.ok(addons[0].isActive, "The add-on is still active.");
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
result = yield experiment.shouldStop();
Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
changes = yield experiment.stop();
Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
// Ensure hash validation works.
// We set an incorrect hash and expect the install to fail.
experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a";
let errored = false;
try {
yield experiment.start();
} catch (ex) {
errored = true;
}
Assert.ok(experiment._failedStart, "Experiment failed to start.");
Assert.ok(errored, "start() threw an exception.");
// Make sure "ignore hashes" mode works.
gPolicy.ignoreHashes = true;
changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL);
yield experiment.stop();
gPolicy.ignoreHashes = false;
});
File diff suppressed because it is too large Load Diff
@@ -1,399 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
const MANIFEST_HANDLER = "manifests/handler";
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
var gHttpServer = null;
var gHttpRoot = null;
var gDataRoot = null;
var gPolicy = null;
var gManifestObject = null;
var gManifestHandlerURI = null;
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
yield removeCacheFile();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gDataRoot = gHttpRoot + "data/";
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
gHttpServer.registerDirectory("/data/", do_get_cwd());
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(gManifestObject));
response.processAsync();
response.finish();
});
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
oneshotTimer: (callback, timeout, thisObj, name) => {},
});
});
function checkExperimentListsEqual(list, list2) {
Assert.equal(list.length, list2.length, "Lists should have the same length.")
for (let i=0; i<list.length; ++i) {
for (let k of Object.keys(list[i])) {
Assert.equal(list[i][k], list2[i][k],
"Field '" + k + "' should match for list entry " + i + ".");
}
}
}
function checkExperimentSerializations(experimentEntryIterator) {
for (let experiment of experimentEntryIterator) {
let experiment2 = new Experiments.ExperimentEntry(gPolicy);
let jsonStr = JSON.stringify(experiment.toJSON());
Assert.ok(experiment2.initFromCacheData(JSON.parse(jsonStr)),
"Should have initialized successfully from JSON serialization.");
Assert.equal(JSON.stringify(experiment), JSON.stringify(experiment2),
"Object stringifications should match.");
}
}
function validateCache(cachedExperiments, experimentIds) {
let cachedExperimentIds = new Set(cachedExperiments);
Assert.equal(cachedExperimentIds.size, experimentIds.length,
"The number of cached experiments does not match with the provided list");
for (let id of experimentIds) {
Assert.ok(cachedExperimentIds.has(id), "The cache must contain the experiment with id " + id);
}
}
// Set up an experiments instance and check if it is properly restored from cache.
add_task(function* test_cache() {
// The manifest data we test with.
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
{
id: EXPERIMENT2_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
{
id: EXPERIMENT3_ID,
xpiURL: "https://inval.id/foo.xpi",
xpiHash: "sha1:0000000000000000000000000000000000000000",
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
// Setup dates for the experiments.
let baseDate = new Date(2014, 5, 1, 12);
let startDates = [];
let endDates = [];
for (let i=0; i<gManifestObject.experiments.length; ++i) {
let experiment = gManifestObject.experiments[i];
startDates.push(futureDate(baseDate, (50 + (150 * i)) * MS_IN_ONE_DAY));
endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
experiment.startTime = dateToSeconds(startDates[i]);
experiment.endTime = dateToSeconds(endDates[i]);
}
// Data to compare the result of Experiments.getExperiments() against.
let experimentListData = [
{
id: EXPERIMENT2_ID,
name: "Test experiment 2",
description: "And yet another experiment that experiments experimentally.",
},
{
id: EXPERIMENT1_ID,
name: EXPERIMENT1_NAME,
description: "Yet another experiment that experiments experimentally.",
},
];
// Trigger update & re-init, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
let experiments = new Experiments.Experiments(gPolicy);
yield experiments.updateManifest();
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
checkExperimentSerializations(experiments._experiments.values());
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
checkExperimentSerializations(experiments._experiments.values());
// Re-init, clock set for experiment 1 to start.
now = futureDate(startDates[0], 5 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
experimentListData[1].active = true;
experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
checkExperimentListsEqual(experimentListData.slice(1), list);
checkExperimentSerializations(experiments._experiments.values());
let branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
Assert.strictEqual(branch, null);
yield experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch");
branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
Assert.strictEqual(branch, "testbranch");
// Re-init, clock set for experiment 1 to stop.
now = futureDate(now, 20 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
experimentListData[1].active = false;
experimentListData[1].endDate = now.getTime();
checkExperimentListsEqual(experimentListData.slice(1), list);
checkExperimentSerializations(experiments._experiments.values());
branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
Assert.strictEqual(branch, "testbranch");
// Re-init, clock set for experiment 2 to start.
now = futureDate(startDates[1], 20 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
experimentListData[0].active = true;
experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
checkExperimentListsEqual(experimentListData, list);
checkExperimentSerializations(experiments._experiments.values());
// Re-init, clock set for experiment 2 to stop.
now = futureDate(now, 20 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
experimentListData[0].active = false;
experimentListData[0].endDate = now.getTime();
checkExperimentListsEqual(experimentListData, list);
checkExperimentSerializations(experiments._experiments.values());
// Cleanup.
yield experiments._toggleExperimentsEnabled(false);
yield promiseRestartManager();
yield removeCacheFile();
});
add_task(function* test_expiration() {
// The manifest data we test with.
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
{
id: EXPERIMENT2_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
maxActiveSeconds: 50 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
// The 3rd experiment will never run, so it's ok to use experiment's 2 data.
{
id: EXPERIMENT3_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
}
],
};
// Data to compare the result of Experiments.getExperiments() against.
let experimentListData = [
{
id: EXPERIMENT2_ID,
name: "Test experiment 2",
description: "And yet another experiment that experiments experimentally.",
},
{
id: EXPERIMENT1_ID,
name: EXPERIMENT1_NAME,
description: "Yet another experiment that experiments experimentally.",
},
];
// Setup dates for the experiments.
let baseDate = new Date(2014, 5, 1, 12);
let startDates = [];
let endDates = [];
for (let i=0; i<gManifestObject.experiments.length; ++i) {
let experiment = gManifestObject.experiments[i];
// Spread out experiments in time so that one experiment can end and expire while
// the next is still running.
startDates.push(futureDate(baseDate, (50 + (200 * i)) * MS_IN_ONE_DAY));
endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
experiment.startTime = dateToSeconds(startDates[i]);
experiment.endTime = dateToSeconds(endDates[i]);
}
let now = null;
let experiments = null;
let setDateAndRestartExperiments = new Task.async(function* (newDate) {
now = newDate;
defineNow(gPolicy, now);
yield promiseRestartManager();
experiments = new Experiments.Experiments(gPolicy);
yield experiments._run();
});
// Trigger update & re-init, clock set to before any activation.
now = baseDate;
defineNow(gPolicy, now);
experiments = new Experiments.Experiments(gPolicy);
yield experiments.updateManifest();
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Re-init, clock set for experiment 1 to start...
yield setDateAndRestartExperiments(startDates[0]);
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "The first experiment should have started.");
// ... init again, and set the clock so that the first experiment ends.
yield setDateAndRestartExperiments(endDates[0]);
// The experiment just ended, it should still be in the cache, but marked
// as finished.
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
experimentListData[1].active = false;
experimentListData[1].endDate = now.getTime();
checkExperimentListsEqual(experimentListData.slice(1), list);
validateCache([...experiments._experiments.keys()], [EXPERIMENT1_ID, EXPERIMENT2_ID, EXPERIMENT3_ID]);
// Start the second experiment.
yield setDateAndRestartExperiments(startDates[1]);
// The experiments cache should contain the finished experiment and the
// one that's still running.
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
experimentListData[0].active = true;
experimentListData[0].endDate = now.getTime() + 50 * MS_IN_ONE_DAY;
checkExperimentListsEqual(experimentListData, list);
// Move the clock in the future, just 31 days after the start date of the second experiment,
// so that the cache for the first experiment expires and the second experiment is still running.
yield setDateAndRestartExperiments(futureDate(startDates[1], 31 * MS_IN_ONE_DAY));
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
// Make sure that the expired experiment is not reported anymore.
let history = yield experiments.getExperiments();
Assert.equal(history.length, 1, "Experiments older than 180 days must be removed from the cache.");
// Test that we don't write expired experiments in the cache.
yield setDateAndRestartExperiments(now);
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
// The first experiment should be expired and not in the cache, it ended more than
// 180 days ago. We should see the one still running in the cache.
history = yield experiments.getExperiments();
Assert.equal(history.length, 1, "Expired experiments must not be saved to cache.");
checkExperimentListsEqual(experimentListData.slice(0, 1), history);
// Test that experiments that are cached locally but never ran are removed from cache
// when they are removed from the manifest (this is cached data, not really history).
gManifestObject["experiments"] = gManifestObject["experiments"].slice(1, 1);
yield experiments.updateManifest();
validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]);
// Cleanup.
yield experiments._toggleExperimentsEnabled(false);
yield promiseRestartManager();
yield removeCacheFile();
});
@@ -1,102 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://gre/modules/Timer.jsm");
const MANIFEST_HANDLER = "manifests/handler";
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
var gHttpServer = null;
var gHttpRoot = null;
var gDataRoot = null;
var gPolicy = null;
var gManifestObject = null;
var gManifestHandlerURI = null;
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
yield removeCacheFile();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gDataRoot = gHttpRoot + "data/";
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
gHttpServer.registerDirectory("/data/", do_get_cwd());
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(gManifestObject));
response.processAsync();
response.finish();
});
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm");
let Experiments = ExperimentsScope.Experiments;
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
delayCacheWrite: (promise) => {
return new Promise((resolve, reject) => {
promise.then(
(result) => { setTimeout(() => resolve(result), 500); },
(err) => { reject(err); }
);
});
},
});
let now = new Date(2014, 5, 1, 12);
defineNow(gPolicy, now);
let experimentName = "experiment-racybranch.xpi";
let experimentPath = getExperimentPath(experimentName);
let experimentHash = "sha1:" + sha1File(experimentPath);
gManifestObject = {
version: 1,
experiments: [
{
id: "test-experiment-racybranch@tests.mozilla.org",
xpiURL: gDataRoot + "experiment-racybranch.xpi",
xpiHash: experimentHash,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
startTime: dateToSeconds(futureDate(now, -MS_IN_ONE_DAY)),
endTime: dateToSeconds(futureDate(now, MS_IN_ONE_DAY)),
},
],
};
do_print("gManifestObject: " + JSON.stringify(gManifestObject));
// In order for the addon manager to work properly, we hack
// Experiments.instance which is used by the XPIProvider
let experiments = new Experiments.Experiments(gPolicy);
Assert.strictEqual(ExperimentsScope.gExperiments, null);
ExperimentsScope.gExperiments = experiments;
yield experiments.updateManifest();
let active = experiments._getActiveExperiment();
Assert.ok(active);
Assert.equal(active.branch, "racy-set");
Assert.ok(!experiments._dirty);
});
@@ -1,325 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource:///modules/experiments/Experiments.jsm");
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
const SEC_IN_ONE_DAY = 24 * 60 * 60;
var gPolicy = null;
function ManifestEntry(data) {
this.id = EXPERIMENT1_ID;
this.xpiURL = "http://localhost:1/dummy.xpi";
this.xpiHash = EXPERIMENT1_XPI_SHA1;
this.startTime = new Date(2010, 0, 1, 12).getTime() / 1000;
this.endTime = new Date(9001, 0, 1, 12).getTime() / 1000;
this.maxActiveSeconds = SEC_IN_ONE_DAY;
this.appName = ["XPCShell"];
this.channel = ["nightly"];
data = data || {};
for (let k of Object.keys(data)) {
this[k] = data[k];
}
if (!this.endTime) {
this.endTime = this.startTime + 5 * SEC_IN_ONE_DAY;
}
}
function applicableFromManifestData(data, policy) {
let manifestData = new ManifestEntry(data);
let entry = new Experiments.ExperimentEntry(policy);
entry.initFromManifestData(manifestData);
return entry.isApplicable();
}
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
createAppInfo();
do_get_profile();
startAddonManagerOnly();
yield TelemetryController.testSetup();
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
locale: () => "en-US",
random: () => 0.5,
});
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
});
function arraysEqual(a, b) {
if (a.length !== b.length) {
return false;
}
for (let i=0; i<a.length; ++i) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
// This function exists solely to be .toSource()d
const sanityFilter = function filter(c) {
if (c.telemetryEnvironment === undefined) {
throw Error("No .telemetryEnvironment");
}
if (c.telemetryEnvironment.build == undefined) {
throw Error("No .telemetryEnvironment.build");
}
return true;
}
// Utility function to generate build ID for previous/next date.
function addDate(buildId, diff) {
let m = /^([0-9]{4})([0-9]{2})([0-9]{2})(.*)$/.exec(buildId);
if (!m) {
throw Error("Unsupported build ID: " + buildId);
}
let year = Number.parseInt(m[1], 10);
let month = Number.parseInt(m[2], 10);
let date = Number.parseInt(m[3], 10);
let remainingParts = m[4];
let d = new Date();
d.setUTCFullYear(year, month - 1, date);
d.setTime(d.getTime() + diff * 24 * 60 * 60 * 1000);
let yearStr = String(d.getUTCFullYear());
let monthStr = ("0" + String(d.getUTCMonth() + 1)).slice(-2);
let dateStr = ("0" + String(d.getUTCDate())).slice(-2);
return yearStr + monthStr + dateStr + remainingParts;
}
function prevDate(buildId) {
return addDate(buildId, -1);
}
function nextDate(buildId) {
return addDate(buildId, 1);
}
add_task(function* test_simpleFields() {
let testData = [
// "expected applicable?", failure reason or null, manifest data
// misc. environment
[false, ["appName"], {appName: []}],
[false, ["appName"], {appName: ["foo", gAppInfo.name + "-invalid"]}],
[true, null, {appName: ["not-an-app-name", gAppInfo.name]}],
[false, ["os"], {os: []}],
[false, ["os"], {os: ["42", "abcdef"]}],
[true, null, {os: [gAppInfo.OS, "plan9"]}],
[false, ["channel"], {channel: []}],
[false, ["channel"], {channel: ["foo", gPolicy.updatechannel() + "-invalid"]}],
[true, null, {channel: ["not-a-channel", gPolicy.updatechannel()]}],
[false, ["locale"], {locale: []}],
[false, ["locale"], {locale: ["foo", gPolicy.locale + "-invalid"]}],
[true, null, {locale: ["not-a-locale", gPolicy.locale()]}],
// version
[false, ["version"], {version: []}],
[false, ["version"], {version: ["-1", gAppInfo.version + "-invalid", "asdf", "0,4", "99.99", "0.1.1.1"]}],
[true, null, {version: ["99999999.999", "-1", gAppInfo.version]}],
[false, ["minVersion"], {minVersion: "1.0.1"}],
[true, null, {minVersion: "1.0b1"}],
[true, null, {minVersion: "1.0"}],
[true, null, {minVersion: "0.9"}],
[false, ["maxVersion"], {maxVersion: "0.1"}],
[false, ["maxVersion"], {maxVersion: "0.9.9"}],
[false, ["maxVersion"], {maxVersion: "1.0b1"}],
[true, ["maxVersion"], {maxVersion: "1.0"}],
[true, ["maxVersion"], {maxVersion: "1.7pre"}],
// build id
[false, ["buildIDs"], {buildIDs: []}],
[false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
[true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
[true, null, {minBuildID: prevDate(gAppInfo.platformBuildID)}],
[true, null, {minBuildID: gAppInfo.platformBuildID}],
[false, ["minBuildID"], {minBuildID: nextDate(gAppInfo.platformBuildID)}],
[false, ["maxBuildID"], {maxBuildID: prevDate(gAppInfo.platformBuildID)}],
[true, null, {maxBuildID: gAppInfo.platformBuildID}],
[true, null, {maxBuildID: nextDate(gAppInfo.platformBuildID)}],
// sample
[false, ["sample"], {sample: -1 }],
[false, ["sample"], {sample: 0.0}],
[false, ["sample"], {sample: 0.1}],
[true, null, {sample: 0.5}],
[true, null, {sample: 0.6}],
[true, null, {sample: 1.0}],
[true, null, {sample: 0.5}],
// experiment control
[false, ["disabled"], {disabled: true}],
[true, null, {disabled: false}],
[false, ["frozen"], {frozen: true}],
[true, null, {frozen: false}],
[false, null, {frozen: true, disabled: true}],
[false, null, {frozen: true, disabled: false}],
[false, null, {frozen: false, disabled: true}],
[true, null, {frozen: false, disabled: false}],
// jsfilter
[true, null, {jsfilter: "function filter(c) { return true; }"}],
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}],
[true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return ''; }"}], // falsy
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { var a = []; }"}], // undefined
[false, ["jsfilter-threw", "some error"], {jsfilter: "function filter(c) { throw new Error('some error'); }"}],
[false, ["jsfilter-evalfailed"], {jsfilter: "123, this won't work"}],
[true, null, {jsfilter: "var filter = " + sanityFilter.toSource()}],
];
for (let i=0; i<testData.length; ++i) {
let entry = testData[i];
let applicable;
let reason = null;
yield applicableFromManifestData(entry[2], gPolicy).then(
value => applicable = value,
value => {
applicable = false;
reason = value;
}
);
Assert.equal(applicable, entry[0],
"Experiment entry applicability should match for test "
+ i + ": " + JSON.stringify(entry[2]));
let expectedReason = entry[1];
if (!applicable && expectedReason) {
Assert.ok(arraysEqual(reason, expectedReason),
"Experiment rejection reasons should match for test " + i + ". "
+ "Got " + JSON.stringify(reason) + ", expected "
+ JSON.stringify(expectedReason));
}
}
});
add_task(function* test_times() {
let now = new Date(2014, 5, 6, 12);
let nowSec = now.getTime() / 1000;
let testData = [
// "expected applicable?", rejection reason or null, fake now date, manifest data
// start time
[true, null, now,
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{startTime: nowSec,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[false, "startTime", now,
{startTime: nowSec + 5 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
// end time
[false, "endTime", now,
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
endTime: nowSec - 10 * SEC_IN_ONE_DAY}],
[false, "endTime", now,
{startTime: nowSec - 5 * SEC_IN_ONE_DAY,
endTime: nowSec - 5 * SEC_IN_ONE_DAY}],
// max start time
[false, "maxStartTime", now,
{maxStartTime: nowSec - 15 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[false, "maxStartTime", now,
{maxStartTime: nowSec - 1 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[false, "maxStartTime", now,
{maxStartTime: nowSec - 10 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{maxStartTime: nowSec,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{maxStartTime: nowSec + 1 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
// max active seconds
[true, null, now,
{maxActiveSeconds: 5 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{maxActiveSeconds: 15 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
[true, null, now,
{maxActiveSeconds: 20 * SEC_IN_ONE_DAY,
startTime: nowSec - 10 * SEC_IN_ONE_DAY,
endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
];
for (let i=0; i<testData.length; ++i) {
let entry = testData[i];
let applicable;
let reason = null;
defineNow(gPolicy, entry[2]);
yield applicableFromManifestData(entry[3], gPolicy).then(
value => applicable = value,
value => {
applicable = false;
reason = value;
}
);
Assert.equal(applicable, entry[0],
"Experiment entry applicability should match for test "
+ i + ": " + JSON.stringify([entry[2], entry[3]]));
if (!applicable && entry[1]) {
Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i);
}
}
});
add_task(function* test_shutdown() {
yield TelemetryController.testShutdown();
});
@@ -1,180 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
const MANIFEST_HANDLER = "manifests/handler";
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
var gHttpServer = null;
var gHttpRoot = null;
var gDataRoot = null;
var gPolicy = null;
var gManifestObject = null;
var gManifestHandlerURI = null;
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gDataRoot = gHttpRoot + "data/";
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
gHttpServer.registerDirectory("/data/", do_get_cwd());
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(gManifestObject));
response.processAsync();
response.finish();
});
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
gPolicy = new Experiments.Policy();
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
oneshotTimer: (callback, timeout, thisObj, name) => {},
});
});
// Test disabling the feature stops current and future experiments.
add_task(function* test_disableExperiments() {
const OBSERVER_TOPIC = "experiments-changed";
let observerFireCount = 0;
let expectedObserverFireCount = 0;
let observer = () => ++observerFireCount;
Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
// Dates the following tests are based on.
let baseDate = new Date(2014, 5, 1, 12);
let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
// The manifest data we test with.
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT2_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
startTime: dateToSeconds(startDate2),
endTime: dateToSeconds(endDate2),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: dateToSeconds(startDate1),
endTime: dateToSeconds(endDate1),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
// Use updateManifest() to provide for coverage of that path.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
Assert.equal(observerFireCount, ++expectedObserverFireCount,
"Experiments observer should have been called.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
// Trigger update, clock set for experiment 1 to start.
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
Assert.equal(observerFireCount, ++expectedObserverFireCount,
"Experiments observer should have been called.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
Assert.equal(list[0].active, true, "Experiment should be active.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "An experiment add-on was installed.");
// Disable the experiments feature. Check that we stop the running experiment.
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, false);
yield experiments._mainTask;
Assert.equal(observerFireCount, ++expectedObserverFireCount,
"Experiments observer should have been called.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
Assert.equal(list[0].active, false, "Experiment entry should not be active.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
// Trigger update, clock set for experiment 2 to start. Verify we don't start it.
now = startDate2;
defineNow(gPolicy, now);
try {
yield experiments.updateManifest();
} catch (e) {
// This exception is expected, we rethrow everything else
if (e.message != "experiments are disabled") {
throw e;
}
}
experiments.notify();
yield experiments._mainTask;
Assert.equal(observerFireCount, expectedObserverFireCount,
"Experiments observer should not have been called.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
Assert.equal(list[0].active, false, "Experiment entry should not be active.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "There should still be no experiment add-on installed.");
// Cleanup.
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
yield promiseRestartManager();
yield removeCacheFile();
});
@@ -1,68 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource:///modules/experiments/Experiments.jsm");
var gHttpServer = null;
var gHttpRoot = null;
var gPolicy = new Experiments.Policy();
function run_test() {
loadAddonManager();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gHttpServer.registerDirectory("/", do_get_cwd());
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
});
run_next_test();
}
add_task(function* test_fetchAndCache() {
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
let ex = new Experiments.Experiments(gPolicy);
Assert.equal(ex._experiments, null, "There should be no cached experiments yet.");
yield ex.updateManifest();
Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
yield promiseRestartManager();
});
add_task(function* test_checkCache() {
let ex = new Experiments.Experiments(gPolicy);
yield ex.notify();
Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
yield promiseRestartManager();
});
add_task(function* test_fetchInvalid() {
yield removeCacheFile();
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
let ex = new Experiments.Experiments(gPolicy);
yield ex.updateManifest();
Assert.notEqual(ex._experiments.size, 0, "There should be experiments");
Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "invalid.manifest");
yield ex.updateManifest()
Assert.notEqual(ex._experiments.size, 0, "There should still be experiments: fetch failure shouldn't remove them.");
yield promiseRestartManager();
});
@@ -1,47 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource:///modules/experiments/Experiments.jsm");
const MANIFEST_HANDLER = "manifests/handler";
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
do_get_profile();
let httpServer = new HttpServer();
httpServer.start(-1);
let port = httpServer.identity.primaryPort;
let httpRoot = "http://localhost:" + port + "/";
let handlerURI = httpRoot + MANIFEST_HANDLER;
httpServer.registerPathHandler("/" + MANIFEST_HANDLER,
(request, response) => {
response.processAsync();
response.setStatus(null, 200, "OK");
response.write("["); // never finish!
});
do_register_cleanup(() => httpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setCharPref(PREF_MANIFEST_URI, handlerURI);
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
let experiments = Experiments.instance();
experiments.updateManifest().then(
() => {
Assert.ok(true, "updateManifest finished successfully");
},
(e) => {
do_throw("updateManifest should not have failed: got error " + e);
});
yield experiments.uninit();
});
@@ -1,179 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource:///modules/experiments/Experiments.jsm");
Cu.import("resource://testing-common/httpd.js");
var gDataRoot;
var gHttpServer;
var gManifestObject;
function run_test() {
run_next_test();
}
add_task(function test_setup() {
loadAddonManager();
do_get_profile();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let httpRoot = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
gDataRoot = httpRoot + "data/";
gHttpServer.registerDirectory("/data/", do_get_cwd());
gHttpServer.registerPathHandler("/manifests/handler", (req, res) => {
res.setStatusLine(null, 200, "OK");
res.write(JSON.stringify(gManifestObject));
res.processAsync();
res.finish();
});
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref("experiments.enabled", true);
Services.prefs.setCharPref("experiments.manifest.uri",
httpRoot + "manifests/handler");
Services.prefs.setBoolPref("experiments.logging.dump", true);
Services.prefs.setCharPref("experiments.logging.level", "Trace");
});
add_task(function* test_provider_basic() {
let e = Experiments.instance();
let provider = new Experiments.PreviousExperimentProvider(e);
e._setPreviousExperimentsProvider(provider);
let deferred = Promise.defer();
provider.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
let experimentAddons = yield deferred.promise;
Assert.ok(Array.isArray(experimentAddons), "getAddonsByTypes returns an Array.");
Assert.equal(experimentAddons.length, 0, "No previous add-ons returned.");
gManifestObject = {
version: 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: Date.now() / 1000 - 60,
endTime: Date.now() / 1000 + 60,
maxActiveSeconds: 60,
appName: ["XPCShell"],
channel: [e._policy.updatechannel()],
},
],
};
yield e.updateManifest();
deferred = Promise.defer();
provider.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
experimentAddons = yield deferred.promise;
Assert.equal(experimentAddons.length, 0, "Still no previous experiment.");
let experiments = yield e.getExperiments();
Assert.equal(experiments.length, 1, "1 experiment present.");
Assert.ok(experiments[0].active, "It is active.");
// Deactivate it.
defineNow(e._policy, new Date(gManifestObject.experiments[0].endTime * 1000 + 1000));
yield e.updateManifest();
experiments = yield e.getExperiments();
Assert.equal(experiments.length, 1, "1 experiment present.");
Assert.equal(experiments[0].active, false, "It isn't active.");
deferred = Promise.defer();
provider.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
experimentAddons = yield deferred.promise;
Assert.equal(experimentAddons.length, 1, "1 previous add-on known.");
Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
deferred = Promise.defer();
provider.getAddonByID(EXPERIMENT1_ID, (addon) => {
deferred.resolve(addon);
});
let addon = yield deferred.promise;
Assert.ok(addon, "We got an add-on from its ID.");
Assert.equal(addon.id, EXPERIMENT1_ID, "ID matches expected.");
Assert.ok(addon.appDisabled, "Add-on is a previous experiment.");
Assert.ok(addon.userDisabled, "Add-on is disabled.");
Assert.equal(addon.type, "experiment", "Add-on is an experiment.");
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.equal(addon.permissions, 0, "Add-on has no permissions.");
deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
experimentAddons = yield deferred.promise;
Assert.equal(experimentAddons.length, 1, "Got 1 experiment from add-on manager.");
Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
Assert.ok(experimentAddons[0].appDisabled, "It is a previous experiment add-on.");
});
add_task(function* test_active_and_previous() {
// Building on the previous test, activate experiment 2.
let e = Experiments.instance();
let provider = new Experiments.PreviousExperimentProvider(e);
e._setPreviousExperimentsProvider(provider);
gManifestObject = {
version: 1,
experiments: [
{
id: EXPERIMENT2_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
startTime: Date.now() / 1000 - 60,
endTime: Date.now() / 1000 + 60,
maxActiveSeconds: 60,
appName: ["XPCShell"],
channel: [e._policy.updatechannel()],
},
],
};
defineNow(e._policy, new Date());
yield e.updateManifest();
let experiments = yield e.getExperiments();
Assert.equal(experiments.length, 2, "2 experiments known.");
let deferred = Promise.defer();
provider.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
let experimentAddons = yield deferred.promise;
Assert.equal(experimentAddons.length, 1, "1 previous experiment.");
deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
experimentAddons = yield deferred.promise;
Assert.equal(experimentAddons.length, 2, "2 experiment add-ons known.");
for (let addon of experimentAddons) {
if (addon.id == EXPERIMENT1_ID) {
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.ok(addon.appDisabled, "Should be a previous experiment.");
}
else if (addon.id == EXPERIMENT2_ID) {
Assert.ok(addon.isActive, "Add-on is active.");
Assert.ok(!addon.appDisabled, "Should not be a previous experiment.");
}
else {
throw new Error("Unexpected add-on ID: " + addon.id);
}
}
});
@@ -1,294 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://gre/modules/TelemetryLog.jsm");
var bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
const MANIFEST_HANDLER = "manifests/handler";
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
var gHttpServer = null;
var gHttpRoot = null;
var gDataRoot = null;
var gPolicy = null;
var gManifestObject = null;
var gManifestHandlerURI = null;
const TLOG = bsp.TELEMETRY_LOG;
function checkEvent(event, id, data)
{
do_print("Checking message " + id);
Assert.equal(event[0], id, "id should match");
Assert.ok(event[1] > 0, "timestamp should be greater than 0");
if (data === undefined) {
Assert.equal(event.length, 2, "event array should have 2 entries");
} else {
Assert.equal(event.length, data.length + 2, "event entry count should match expected count");
for (var i = 0; i < data.length; ++i) {
Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string");
Assert.equal(event[i + 2], data[i], "event entry should match expected entry");
}
}
}
function run_test() {
run_next_test();
}
add_task(function* test_setup() {
loadAddonManager();
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let port = gHttpServer.identity.primaryPort;
gHttpRoot = "http://localhost:" + port + "/";
gDataRoot = gHttpRoot + "data/";
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
gHttpServer.registerDirectory("/data/", do_get_cwd());
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(gManifestObject));
response.processAsync();
response.finish();
});
do_register_cleanup(() => gHttpServer.stop(() => {}));
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
gPolicy = new Experiments.Policy();
let dummyTimer = { cancel: () => {}, clear: () => {} };
patchPolicy(gPolicy, {
updatechannel: () => "nightly",
oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer,
});
yield removeCacheFile();
});
// Test basic starting and stopping of experiments.
add_task(function* test_telemetryBasics() {
// Check TelemetryLog instead of TelemetrySession.getPayload().log because
// TelemetrySession gets Experiments.instance() and side-effects log entries.
let expectedLogLength = 0;
// Dates the following tests are based on.
let baseDate = new Date(2014, 5, 1, 12);
let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
// The manifest data we test with.
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: dateToSeconds(startDate1),
endTime: dateToSeconds(endDate1),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
{
id: EXPERIMENT2_ID,
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
xpiHash: EXPERIMENT2_XPI_SHA1,
startTime: dateToSeconds(startDate2),
endTime: dateToSeconds(endDate2),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
// Use updateManifest() to provide for coverage of that path.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
expectedLogLength += 2;
let log = TelemetryLog.entries();
do_print("Telemetry log: " + JSON.stringify(log));
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]);
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
// Trigger update, clock set for experiment 1 to start.
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource());
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]);
// Trigger update, clock set for experiment 1 to stop.
now = futureDate(endDate1, 1000);
defineNow(gPolicy, now);
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
expectedLogLength += 2;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-2], TLOG.TERMINATION_KEY,
[TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]);
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
// Trigger update, clock set for experiment 2 to start with invalid hash.
now = startDate2;
defineNow(gPolicy, now);
gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000";
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]);
// Trigger update, clock set for experiment 2 to properly start now.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1;
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
// Fake user uninstall of experiment via add-on manager.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
[TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
// Trigger update with experiment 1a ready to start.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gManifestObject.experiments[0].id = EXPERIMENT3_ID;
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
// Trigger disable of an experiment via the API.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
list = yield experiments.getExperiments();
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
[TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
// Trigger update with experiment 1a ready to start.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gManifestObject.experiments[0].id = EXPERIMENT4_ID;
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]);
// Trigger experiment termination by something other than expiry via the manifest.
now = futureDate(now, MS_IN_ONE_DAY);
defineNow(gPolicy, now);
gManifestObject.experiments[0].os = "Plan9";
yield experiments.updateManifest();
list = yield experiments.getExperiments();
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
expectedLogLength += 1;
log = TelemetryLog.entries();
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
[TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]);
// Cleanup.
yield promiseRestartManager();
yield removeCacheFile();
});
@@ -1,21 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource:///modules/experiments/Experiments.jsm");
add_test(function test_experiments_activation() {
do_get_profile();
loadAddonManager();
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
let experiments = Experiments.instance();
Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
// TODO: Test that Experiments are turned back on when bug 1232648 lands.
run_next_test();
});
@@ -1,52 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource:///modules/experiments/Experiments.jsm");
var cacheData = {
_enabled: true,
_manifestData: {
id: "foobartestid",
xpiURL: "http://example.com/foo.xpi",
xpiHash: "sha256:abcde",
startTime: 0,
endTime: 2000000000,
maxActiveSeconds: 40000000,
appName: "TestApp",
channel: "test-foo",
},
_needsUpdate: false,
_randomValue: 0.5,
_failedStart: false,
_name: "Foo",
_description: "Foobar",
_homepageURL: "",
_addonId: "foo@test",
_startDate: 0,
_endDate: 2000000000,
_branch: null
};
add_task(function* test_valid() {
let e = new Experiments.ExperimentEntry();
Assert.ok(e.initFromCacheData(cacheData));
Assert.ok(e.enabled);
});
add_task(function* test_upgrade() {
let e = new Experiments.ExperimentEntry();
delete cacheData._branch;
Assert.ok(e.initFromCacheData(cacheData));
Assert.ok(e.enabled);
});
add_task(function* test_missing() {
let e = new Experiments.ExperimentEntry();
delete cacheData._name;
Assert.ok(!e.initFromCacheData(cacheData));
});
function run_test() {
run_next_test();
}
@@ -1,31 +0,0 @@
[DEFAULT]
head = head.js
tail =
tags = addons
firefox-appdir = browser
skip-if = toolkit == 'android'
support-files =
experiments_1.manifest
experiment-1.xpi
experiment-1a.xpi
experiment-2.xpi
experiment-racybranch.xpi
!/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js
generated-files =
experiment-1.xpi
experiment-1a.xpi
experiment-2.xpi
experiment-racybranch.xpi
[test_activate.js]
[test_api.js]
[test_cache.js]
[test_cacherace.js]
[test_conditions.js]
[test_disableExperiments.js]
[test_fetch.js]
[test_telemetry.js]
[test_telemetry_disabled.js]
[test_previous_provider.js]
[test_upgrade.js]
[test_nethang_bug1012924.js]
-1
View File
@@ -11,7 +11,6 @@ SPHINX_TREES['browser'] = 'docs'
DIRS += [
'base',
'components',
'experiments',
'fonts',
'locales',
'modules',
@@ -153,7 +153,6 @@ const DEFAULT_ENVIRONMENT_PREFS = new Map([
["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}],
["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}],
["extensions.enabledScopes", {what: RECORD_PREF_VALUE}],
["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}],
@@ -209,7 +208,6 @@ const PREF_E10S_COHORT = "e10s.rollout.cohort";
const COMPOSITOR_CREATED_TOPIC = "compositor:created";
const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete";
const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
const SEARCH_SERVICE_TOPIC = "browser-search-service";
@@ -465,7 +463,6 @@ EnvironmentAddonBuilder.prototype = {
watchForChanges: function() {
this._loaded = true;
AddonManager.addAddonListener(this);
Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false);
},
// AddonListener
@@ -490,7 +487,6 @@ EnvironmentAddonBuilder.prototype = {
// nsIObserver
observe: function (aSubject, aTopic, aData) {
this._environment._log.trace("observe - Topic " + aTopic);
this._checkForChanges("experiment-changed");
},
_checkForChanges: function(changeReason) {
@@ -515,7 +511,6 @@ EnvironmentAddonBuilder.prototype = {
_shutdownBlocker: function() {
if (this._loaded) {
AddonManager.removeAddonListener(this);
Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC);
}
return this._pendingTask;
},
@@ -545,7 +540,6 @@ EnvironmentAddonBuilder.prototype = {
theme: yield this._getActiveTheme(),
activePlugins: this._getActivePlugins(),
activeGMPlugins: yield this._getActiveGMPlugins(),
activeExperiment: this._getActiveExperiment(),
persona: personaId,
};
@@ -718,29 +712,7 @@ EnvironmentAddonBuilder.prototype = {
}
return activeGMPlugins;
}),
/**
* Get the active experiment data in object form.
* @return Object containing the active experiment data.
*/
_getActiveExperiment: function () {
let experimentInfo = {};
try {
let scope = {};
Cu.import("resource:///modules/experiments/Experiments.jsm", scope);
let experiments = scope.Experiments.instance();
let activeExperiment = experiments.getActiveExperimentID();
if (activeExperiment) {
experimentInfo.id = activeExperiment;
experimentInfo.branch = experiments.getActiveExperimentBranch();
}
} catch (e) {
// If this is not Firefox, the import will fail.
}
return experimentInfo;
},
})
};
function EnvironmentCache() {
-33
View File
@@ -504,39 +504,6 @@
</table>
<h2 class="major-section">
&aboutSupport.experimentsTitle;
</h2>
<table>
<thead>
<tr>
<th>
&aboutSupport.experimentName;
</th>
<th>
&aboutSupport.experimentId;
</th>
<th>
&aboutSupport.experimentDescription;
</th>
<th>
&aboutSupport.experimentActive;
</th>
<th>
&aboutSupport.experimentEndDate;
</th>
<th>
&aboutSupport.experimentHomepage;
</th>
<th>
&aboutSupport.experimentBranch;
</th>
</tr>
</thead>
<tbody id="experiments-tbody">
</tbody>
</table>
<!-- - - - - - - - - - - - - - - - - - - - - -->
#if defined(MOZ_SANDBOX)
-19
View File
@@ -12,13 +12,6 @@ Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
var Experiments;
try {
Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments;
}
catch (e) {
}
// We use a preferences whitelist to make sure we only show preferences that
// are useful for support and won't compromise the user's privacy. Note that
// entries are *prefixes*: for example, "accessibility." applies to all prefs
@@ -263,18 +256,6 @@ var dataProviders = {
});
},
experiments: function experiments(done) {
if (Experiments === undefined) {
done([]);
return;
}
// getExperiments promises experiment history
Experiments.instance().getExperiments().then(
experiments => done(experiments)
);
},
modifiedPreferences: function modifiedPreferences(done) {
done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
},
@@ -22,8 +22,6 @@ XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
return Cu.import("resource://gre/modules/devtools/ToolboxProcess.jsm", {}).
BrowserToolboxProcess;
});
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
@@ -216,23 +214,6 @@ function isDiscoverEnabled() {
return true;
}
function getExperimentEndDate(aAddon) {
if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
return 0;
}
if (!aAddon.isActive) {
return aAddon.endDate;
}
let experiment = Experiments.instance().getActiveExperiment();
if (!experiment) {
return 0;
}
return experiment.endDate;
}
/**
* Obtain the main DOMWindow for the current context.
*/
@@ -1316,28 +1297,7 @@ var gViewController = {
doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
aAddon.userDisabled = true;
}
},
cmd_experimentsLearnMore: {
isEnabled: function cmd_experimentsLearnMore_isEnabled() {
let mainWindow = getMainWindow();
return mainWindow && "switchToTabHavingURI" in mainWindow;
},
doCommand: function cmd_experimentsLearnMore_doCommand() {
let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
openOptionsInTab(url);
},
},
cmd_experimentsOpenTelemetryPreferences: {
isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() {
return !!getMainWindowWithPreferencesPane();
},
doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() {
let mainWindow = getMainWindowWithPreferencesPane();
mainWindow.openAdvancedPreferences("dataChoicesTab");
},
},
}
},
supportsCommand: function gVC_supportsCommand(aCommand) {
@@ -1468,10 +1428,6 @@ function createItem(aObj, aIsInstall, aIsRemote) {
// the binding handles the rest
item.setAttribute("value", aObj.id);
if (aObj.type == "experiment") {
item.endDate = getExperimentEndDate(aObj);
}
return item;
}
@@ -2679,13 +2635,6 @@ var gListView = {
// the existing item
if (aInstall.existingAddon)
this.removeItem(aInstall, true);
if (aInstall.addon.type == "experiment") {
let item = this.getListItemForID(aInstall.addon.id);
if (item) {
item.endDate = getExperimentEndDate(aInstall.addon);
}
}
},
addItem: function gListView_addItem(aObj, aIsInstall) {
@@ -2945,34 +2894,6 @@ var gDetailView = {
}
}
if (this._addon.type == "experiment") {
let prefix = "details.experiment.";
let active = this._addon.isActive;
let stateKey = prefix + "state." + (active ? "active" : "complete");
let node = document.getElementById("detail-experiment-state");
node.value = gStrings.ext.GetStringFromName(stateKey);
let now = Date.now();
let end = getExperimentEndDate(this._addon);
let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
let timeKey = prefix + "time.";
let timeMessage;
if (days < 1) {
timeKey += (active ? "endsToday" : "endedToday");
timeMessage = gStrings.ext.GetStringFromName(timeKey);
} else {
timeKey += (active ? "daysRemaining" : "daysPassed");
days = Math.round(days);
let timeString = gStrings.ext.GetStringFromName(timeKey);
timeMessage = PluralForm.get(days, timeString)
.replace("#1", days);
}
document.getElementById("detail-experiment-time").value = timeMessage;
}
this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
this.updateState();
gViewController.notifyViewChanged();
@@ -1,645 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
Components.utils.import("resource://gre/modules/Promise.jsm", this);
let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
let {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
let gManagerWindow;
let gCategoryUtilities;
let gExperiments;
let gHttpServer;
let gSavedManifestURI;
let gIsEnUsLocale;
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
function getExperimentAddons() {
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
return deferred.promise;
}
function getInstallItem() {
let doc = gManagerWindow.document;
let view = doc.getElementById("view-port").selectedPanel;
let list = doc.getElementById("addon-list");
let node = list.firstChild;
while (node) {
if (node.getAttribute("status") == "installing") {
return node;
}
node = node.nextSibling;
}
return null;
}
function patchPolicy(policy, data) {
for (let key of Object.keys(data)) {
Object.defineProperty(policy, key, {
value: data[key],
writable: true,
});
}
}
function defineNow(policy, time) {
patchPolicy(policy, { now: () => new Date(time) });
}
function openDetailsView(aId) {
let item = get_addon_element(gManagerWindow, aId);
Assert.ok(item, "Should have got add-on element.");
is_element_visible(item, "Add-on element should be visible.");
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow);
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
let deferred = Promise.defer();
wait_for_view_load(gManagerWindow, deferred.resolve);
return deferred.promise;
}
function clickRemoveButton(addonElement) {
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn");
if (!btn) {
return Promise.reject();
}
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
let deferred = Promise.defer();
setTimeout(deferred.resolve, 0);
return deferred;
}
function clickUndoButton(addonElement) {
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn");
if (!btn) {
return Promise.reject();
}
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
let deferred = Promise.defer();
setTimeout(deferred.resolve, 0);
return deferred;
}
add_task(function* initializeState() {
gManagerWindow = yield open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("experiments.enabled");
if (gHttpServer) {
gHttpServer.stop(() => {});
if (gSavedManifestURI !== undefined) {
Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI);
}
}
if (gExperiments) {
let tmp = {};
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
gExperiments._policy = new tmp.Experiments.Policy();
}
});
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
// The Experiments Manager will interfere with us by preventing installs
// of experiments it doesn't know about. We remove it from the equation
// because here we are only concerned with core Addon Manager operation,
// not the superset Experiments Manager has imposed.
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
let tmp = {};
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
// There is a race condition between XPCOM service initialization and
// this test running. We have to initialize the instance first, then
// uninitialize it to prevent this.
gExperiments = tmp.Experiments.instance();
yield gExperiments._mainTask;
yield gExperiments.uninit();
}
});
// On an empty profile with no experiments, the experiment category
// should be hidden.
add_task(function* testInitialState() {
Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
});
add_task(function* testExperimentInfoNotVisible() {
yield gCategoryUtilities.openType("extension");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_hidden(el, "Experiment info not visible on other types.");
});
// If we have an active experiment, we should see the experiments tab
// and that tab should have some messages.
add_task(function* testActiveExperiment() {
let addon = yield install_addon("addons/browser_experiment1.xpi");
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
yield gCategoryUtilities.openType("experiment");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
});
add_task(function* testExperimentLearnMore() {
// Actual URL is irrelevant.
Services.prefs.setCharPref("toolkit.telemetry.infoURL",
"http://mochi.test:8888/server.js");
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
if (!gUseInContentUI) {
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
return;
}
is_element_visible(btn, "Learn more button visible.");
let deferred = Promise.defer();
window.addEventListener("DOMContentLoaded", function onLoad(event) {
info("Telemetry privacy policy window opened.");
window.removeEventListener("DOMContentLoaded", onLoad, false);
let browser = gBrowser.selectedBrowser;
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
browser.contentWindow.close();
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
deferred.resolve();
}, false);
info("Opening telemetry privacy policy.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
yield deferred.promise;
});
add_task(function* testOpenPreferences() {
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
if (!gUseInContentUI) {
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
info("Skipping preferences open test because not using in-content UI.");
return;
}
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
let deferred = Promise.defer();
Services.obs.addObserver(function observer(prefWin, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
info("Advanced preference pane opened.");
executeSoon(function() {
// We want this test to fail if the preferences pane changes.
let el = prefWin.document.getElementById("dataChoicesPanel");
is_element_visible(el);
prefWin.close();
info("Closed preferences pane.");
deferred.resolve();
});
}, "advanced-pane-loaded", false);
info("Loading preferences pane.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
yield deferred.promise;
});
add_task(function* testButtonPresence() {
yield gCategoryUtilities.openType("experiment");
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
// Corresponds to the uninstall permission.
is_element_visible(el, "Remove button is visible.");
// Corresponds to lack of disable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button not visible.");
// Corresponds to lack of enable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button not visible.");
});
// Remove the add-on we've been testing with.
add_task(function* testCleanup() {
yield AddonTestUtils.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
// Verify some conditions, just in case.
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
});
// The following tests should ideally live in browser/experiments/. However,
// they rely on some of the helper functions from head.js, which can't easily
// be consumed from other directories. So, they live here.
add_task(function* testActivateExperiment() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
gHttpServer.registerPathHandler("/manifest", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({
"version": 1,
"experiments": [
{
id: "experiment-1",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
}));
response.processAsync();
response.finish();
});
gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri");
Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
// We need to remove the cache file to help ensure consistent state.
yield OS.File.remove(gExperiments._cacheFilePath);
Services.prefs.setBoolPref("experiments.enabled", true);
info("Initializing experiments service.");
yield gExperiments.init();
info("Experiments service finished first run.");
// Check conditions, just to be sure.
let experiments = yield gExperiments.getExperiments();
Assert.equal(experiments.length, 0, "No experiments known to the service.");
// This makes testing easier.
gExperiments._policy.ignoreHashes = true;
info("Manually updating experiments manifest.");
yield gExperiments.updateManifest();
info("Experiments update complete.");
let deferred = Promise.defer();
gHttpServer.stop(() => {
gHttpServer = null;
info("getting experiment by ID");
AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => {
Assert.ok(addon, "Add-on installed via Experiments manager.");
deferred.resolve();
});
});
yield deferred.promise;
Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible.");
yield gCategoryUtilities.openType("experiment");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
});
add_task(function testDeactivateExperiment() {
if (!gExperiments) {
return;
}
// Fake an empty manifest to purge data from previous manifest.
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [],
});
yield gExperiments.disableExperiment("testing");
// We should have a record of the previously-active experiment.
let experiments = yield gExperiments.getExperiments();
Assert.equal(experiments.length, 1, "1 experiment is known.");
Assert.equal(experiments[0].active, false, "Experiment is not active.");
// We should have a previous experiment in the add-ons manager.
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
let addons = yield deferred.promise;
Assert.equal(addons.length, 1, "1 experiment add-on known.");
Assert.ok(addons[0].appDisabled, "It is a previous experiment.");
Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected.");
// Verify the UI looks sane.
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
let item = get_addon_element(gManagerWindow, "experiment-1");
Assert.ok(item, "Got add-on element.");
Assert.ok(!item.active, "Element should not be active.");
item.parentNode.ensureElementIsVisible(item);
// User control buttons should not be present because previous experiments
// should have no permissions.
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
is_element_hidden(el, "Remove button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
is_element_hidden(el, "Preferences button is not visible.");
});
add_task(function testActivateRealExperiments() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-2",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check the active experiment.
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day remaining");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
is_element_hidden(el, "error-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
is_element_hidden(el, "warning-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
is_element_hidden(el, "pending-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
is_element_hidden(el, "version should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
is_element_hidden(el, "disabled-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
is_element_hidden(el, "update-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Check the previous experiment.
item = get_addon_element(gManagerWindow, "experiment-1");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day ago");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
is_element_hidden(el, "error-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
is_element_hidden(el, "warning-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
is_element_hidden(el, "pending-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
is_element_hidden(el, "version should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
is_element_hidden(el, "disabled-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
is_element_hidden(el, "update-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Install an "older" experiment.
yield gExperiments.disableExperiment("experiment-2");
let now = Date.now();
let fakeNow = now - 5 * MS_IN_ONE_DAY;
defineNow(gExperiments._policy, fakeNow);
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-3",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: fakeNow / 1000 - SEC_IN_ONE_DAY,
endTime: now / 1000 + 10 * SEC_IN_ONE_DAY,
maxActiveSeconds: 100 * SEC_IN_ONE_DAY,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check the active experiment.
item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "10 days remaining");
}
// Disable it and check it's previous experiment entry.
yield gExperiments.disableExperiment("experiment-3");
item = get_addon_element(gManagerWindow, "experiment-3");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "5 days ago");
}
});
add_task(function testDetailView() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
defineNow(gExperiments._policy, Date.now());
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-4",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check active experiment.
yield openDetailsView("test-experiment1@experiments.mozilla.org");
let el = gManagerWindow.document.getElementById("detail-experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = gManagerWindow.document.getElementById("detail-experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day remaining");
}
el = gManagerWindow.document.getElementById("detail-version");
is_element_hidden(el, "detail-version should be hidden.");
el = gManagerWindow.document.getElementById("detail-creator");
is_element_hidden(el, "detail-creator should be hidden.");
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Check previous experiment.
yield gCategoryUtilities.openType("experiment");
yield openDetailsView("experiment-3");
el = gManagerWindow.document.getElementById("detail-experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = gManagerWindow.document.getElementById("detail-experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "5 days ago");
}
el = gManagerWindow.document.getElementById("detail-version");
is_element_hidden(el, "detail-version should be hidden.");
el = gManagerWindow.document.getElementById("detail-creator");
is_element_hidden(el, "detail-creator should be hidden.");
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
});
add_task(function* testRemoveAndUndo() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
yield gCategoryUtilities.openType("experiment");
let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(addon, "Got add-on element.");
yield clickRemoveButton(addon);
addon.parentNode.ensureElementIsVisible(addon);
let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending");
is_element_visible(el, "Uninstall undo information should be visible.");
yield clickUndoButton(addon);
addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(addon, "Got add-on element.");
});
add_task(function* testCleanup() {
if (gExperiments) {
Services.prefs.clearUserPref("experiments.enabled");
Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI);
// We perform the uninit/init cycle to purge any leftover state.
yield OS.File.remove(gExperiments._cacheFilePath);
yield gExperiments.uninit();
yield gExperiments.init();
}
// Check post-conditions.
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
yield close_manager(gManagerWindow);
});
@@ -29,9 +29,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
const PREF_XPI_ENABLED = "xpinstall.enabled";
@@ -297,23 +294,6 @@ function isDiscoverEnabled() {
return true;
}
function getExperimentEndDate(aAddon) {
if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
return 0;
}
if (!aAddon.isActive) {
return aAddon.endDate;
}
let experiment = Experiments.instance().getActiveExperiment();
if (!experiment) {
return 0;
}
return experiment.endDate;
}
/**
* Obtain the main DOMWindow for the current context.
*/
@@ -1444,27 +1424,6 @@ var gViewController = {
}
},
cmd_experimentsLearnMore: {
isEnabled: function() {
let mainWindow = getMainWindow();
return mainWindow && "switchToTabHavingURI" in mainWindow;
},
doCommand: function() {
let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
openOptionsInTab(url);
},
},
cmd_experimentsOpenTelemetryPreferences: {
isEnabled: function() {
return !!getMainWindowWithPreferencesPane();
},
doCommand: function() {
let mainWindow = getMainWindowWithPreferencesPane();
mainWindow.openAdvancedPreferences("dataChoicesTab");
},
},
cmd_showUnsignedExtensions: {
isEnabled: function() {
return true;
@@ -1577,10 +1536,6 @@ function shouldShowVersionNumber(aAddon) {
if (!aAddon.version)
return false;
// The version number is hidden for experiments.
if (aAddon.type == "experiment")
return false;
// The version number is hidden for lightweight themes.
if (aAddon.type == "theme")
return !/@personas\.mozilla\.org$/.test(aAddon.id);
@@ -1614,10 +1569,6 @@ function createItem(aObj, aIsInstall, aIsRemote) {
// the binding handles the rest
item.setAttribute("value", aObj.id);
if (aObj.type == "experiment") {
item.endDate = getExperimentEndDate(aObj);
}
return item;
}
@@ -2861,13 +2812,6 @@ var gListView = {
// the existing item
if (aInstall.existingAddon)
this.removeItem(aInstall, true);
if (aInstall.addon.type == "experiment") {
let item = this.getListItemForID(aInstall.addon.id);
if (item) {
item.endDate = getExperimentEndDate(aInstall.addon);
}
}
},
addItem: function(aObj, aIsInstall) {
@@ -3126,34 +3070,6 @@ var gDetailView = {
}
}
if (this._addon.type == "experiment") {
let prefix = "details.experiment.";
let active = this._addon.isActive;
let stateKey = prefix + "state." + (active ? "active" : "complete");
let node = document.getElementById("detail-experiment-state");
node.value = gStrings.ext.GetStringFromName(stateKey);
let now = Date.now();
let end = getExperimentEndDate(this._addon);
let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
let timeKey = prefix + "time.";
let timeMessage;
if (days < 1) {
timeKey += (active ? "endsToday" : "endedToday");
timeMessage = gStrings.ext.GetStringFromName(timeKey);
} else {
timeKey += (active ? "daysRemaining" : "daysPassed");
days = Math.round(days);
let timeString = gStrings.ext.GetStringFromName(timeKey);
timeMessage = PluralForm.get(days, timeString)
.replace("#1", days);
}
document.getElementById("detail-experiment-time").value = timeMessage;
}
this.fillSettingsRows(aScrollToPreferences, (function() {
this.updateState();
gViewController.notifyViewChanged();
@@ -1,654 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
Components.utils.import("resource://gre/modules/Promise.jsm", this);
var {AddonManagerTesting} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
var {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
var gManagerWindow;
var gCategoryUtilities;
var gExperiments;
var gHttpServer;
var gSavedManifestURI;
var gIsEnUsLocale;
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
function getExperimentAddons() {
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
return deferred.promise;
}
function getInstallItem() {
let doc = gManagerWindow.document;
let view = get_current_view(gManagerWindow);
let list = doc.getElementById("addon-list");
let node = list.firstChild;
while (node) {
if (node.getAttribute("status") == "installing") {
return node;
}
node = node.nextSibling;
}
return null;
}
function patchPolicy(policy, data) {
for (let key of Object.keys(data)) {
Object.defineProperty(policy, key, {
value: data[key],
writable: true,
});
}
}
function defineNow(policy, time) {
patchPolicy(policy, { now: () => new Date(time) });
}
function openDetailsView(aId) {
let item = get_addon_element(gManagerWindow, aId);
Assert.ok(item, "Should have got add-on element.");
is_element_visible(item, "Add-on element should be visible.");
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow);
EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
let deferred = Promise.defer();
wait_for_view_load(gManagerWindow, deferred.resolve);
return deferred.promise;
}
function clickRemoveButton(addonElement) {
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "remove-btn");
if (!btn) {
return Promise.reject();
}
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
let deferred = Promise.defer();
setTimeout(deferred.resolve, 0);
return deferred;
}
function clickUndoButton(addonElement) {
let btn = gManagerWindow.document.getAnonymousElementByAttribute(addonElement, "anonid", "undo-btn");
if (!btn) {
return Promise.reject();
}
EventUtils.synthesizeMouseAtCenter(btn, { clickCount: 1 }, gManagerWindow);
let deferred = Promise.defer();
setTimeout(deferred.resolve, 0);
return deferred;
}
add_task(function* initializeState() {
gManagerWindow = yield open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("experiments.enabled");
Services.prefs.clearUserPref("toolkit.telemetry.enabled");
if (gHttpServer) {
gHttpServer.stop(() => {});
if (gSavedManifestURI !== undefined) {
Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI);
}
}
if (gExperiments) {
let tmp = {};
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
gExperiments._policy = new tmp.Experiments.Policy();
}
});
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
// The Experiments Manager will interfere with us by preventing installs
// of experiments it doesn't know about. We remove it from the equation
// because here we are only concerned with core Addon Manager operation,
// not the superset Experiments Manager has imposed.
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
let tmp = {};
Cu.import("resource:///modules/experiments/Experiments.jsm", tmp);
// There is a race condition between XPCOM service initialization and
// this test running. We have to initialize the instance first, then
// uninitialize it to prevent this.
gExperiments = tmp.Experiments.instance();
yield gExperiments._mainTask;
yield gExperiments.uninit();
}
});
// On an empty profile with no experiments, the experiment category
// should be hidden.
add_task(function* testInitialState() {
Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
});
add_task(function* testExperimentInfoNotVisible() {
yield gCategoryUtilities.openType("extension");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_hidden(el, "Experiment info not visible on other types.");
});
// If we have an active experiment, we should see the experiments tab
// and that tab should have some messages.
add_task(function* testActiveExperiment() {
let addon = yield install_addon("addons/browser_experiment1.xpi");
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
yield gCategoryUtilities.openType("experiment");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
});
add_task(function* testExperimentLearnMore() {
// Actual URL is irrelevant.
Services.prefs.setCharPref("toolkit.telemetry.infoURL",
"http://mochi.test:8888/server.js");
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
if (!gUseInContentUI) {
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
return;
}
is_element_visible(btn, "Learn more button visible.");
let deferred = Promise.defer();
window.addEventListener("DOMContentLoaded", function onLoad(event) {
info("Telemetry privacy policy window opened.");
window.removeEventListener("DOMContentLoaded", onLoad, false);
let browser = gBrowser.selectedBrowser;
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
browser.contentWindow.close();
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
deferred.resolve();
}, false);
info("Opening telemetry privacy policy.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
yield deferred.promise;
});
add_task(function* testOpenPreferences() {
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
if (!gUseInContentUI) {
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
info("Skipping preferences open test because not using in-content UI.");
return;
}
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
let deferred = Promise.defer();
Services.obs.addObserver(function observer(prefWin, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
info("Advanced preference pane opened.");
executeSoon(function() {
// We want this test to fail if the preferences pane changes.
let el = prefWin.document.getElementById("dataChoicesPanel");
is_element_visible(el);
prefWin.close();
info("Closed preferences pane.");
deferred.resolve();
});
}, "advanced-pane-loaded", false);
info("Loading preferences pane.");
// We need to focus before synthesizing the mouse event (bug 1240052) as
// synthesizeMouseAtCenter currently only synthesizes the mouse in the child process.
// This can cause some subtle differences if the child isn't focused.
yield SimpleTest.promiseFocus();
yield BrowserTestUtils.synthesizeMouseAtCenter("#experiments-change-telemetry", {},
gBrowser.selectedBrowser);
yield deferred.promise;
});
add_task(function* testButtonPresence() {
yield gCategoryUtilities.openType("experiment");
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
// Corresponds to the uninstall permission.
is_element_visible(el, "Remove button is visible.");
// Corresponds to lack of disable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button not visible.");
// Corresponds to lack of enable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button not visible.");
});
// Remove the add-on we've been testing with.
add_task(function* testCleanup() {
yield AddonManagerTesting.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
// Verify some conditions, just in case.
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
});
// The following tests should ideally live in browser/experiments/. However,
// they rely on some of the helper functions from head.js, which can't easily
// be consumed from other directories. So, they live here.
add_task(function* testActivateExperiment() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
gHttpServer = new HttpServer();
gHttpServer.start(-1);
let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
gHttpServer.registerPathHandler("/manifest", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({
"version": 1,
"experiments": [
{
id: "experiment-1",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
}));
response.processAsync();
response.finish();
});
gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri");
Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
// We need to remove the cache file to help ensure consistent state.
yield OS.File.remove(gExperiments._cacheFilePath);
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
Services.prefs.setBoolPref("experiments.enabled", true);
info("Initializing experiments service.");
yield gExperiments.init();
info("Experiments service finished first run.");
// Check conditions, just to be sure.
let experiments = yield gExperiments.getExperiments();
Assert.equal(experiments.length, 0, "No experiments known to the service.");
// This makes testing easier.
gExperiments._policy.ignoreHashes = true;
info("Manually updating experiments manifest.");
yield gExperiments.updateManifest();
info("Experiments update complete.");
let deferred = Promise.defer();
gHttpServer.stop(() => {
gHttpServer = null;
info("getting experiment by ID");
AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => {
Assert.ok(addon, "Add-on installed via Experiments manager.");
deferred.resolve();
});
});
yield deferred.promise;
Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible.");
yield gCategoryUtilities.openType("experiment");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
});
add_task(function* testDeactivateExperiment() {
if (!gExperiments) {
return;
}
// Fake an empty manifest to purge data from previous manifest.
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [],
});
yield gExperiments.disableExperiment("testing");
// We should have a record of the previously-active experiment.
let experiments = yield gExperiments.getExperiments();
Assert.equal(experiments.length, 1, "1 experiment is known.");
Assert.equal(experiments[0].active, false, "Experiment is not active.");
// We should have a previous experiment in the add-ons manager.
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"], (addons) => {
deferred.resolve(addons);
});
let addons = yield deferred.promise;
Assert.equal(addons.length, 1, "1 experiment add-on known.");
Assert.ok(addons[0].appDisabled, "It is a previous experiment.");
Assert.equal(addons[0].id, "experiment-1", "Add-on ID matches expected.");
// Verify the UI looks sane.
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
let item = get_addon_element(gManagerWindow, "experiment-1");
Assert.ok(item, "Got add-on element.");
Assert.ok(!item.active, "Element should not be active.");
item.parentNode.ensureElementIsVisible(item);
// User control buttons should not be present because previous experiments
// should have no permissions.
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
is_element_hidden(el, "Remove button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button is not visible.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
is_element_hidden(el, "Preferences button is not visible.");
});
add_task(function* testActivateRealExperiments() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-2",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check the active experiment.
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day remaining");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
is_element_hidden(el, "error-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
is_element_hidden(el, "warning-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
is_element_hidden(el, "pending-container should be hidden.");
let { version } = yield get_tooltip_info(item);
Assert.equal(version, undefined, "version should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
is_element_hidden(el, "disabled-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
is_element_hidden(el, "update-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Check the previous experiment.
item = get_addon_element(gManagerWindow, "experiment-1");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day ago");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "error-container");
is_element_hidden(el, "error-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning-container");
is_element_hidden(el, "warning-container should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
is_element_hidden(el, "pending-container should be hidden.");
({ version } = yield get_tooltip_info(item));
Assert.equal(version, undefined, "version should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
is_element_hidden(el, "disabled-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
is_element_hidden(el, "update-postfix should be hidden.");
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Install an "older" experiment.
yield gExperiments.disableExperiment("experiment-2");
let now = Date.now();
let fakeNow = now - 5 * MS_IN_ONE_DAY;
defineNow(gExperiments._policy, fakeNow);
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-3",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: fakeNow / 1000 - SEC_IN_ONE_DAY,
endTime: now / 1000 + 10 * SEC_IN_ONE_DAY,
maxActiveSeconds: 100 * SEC_IN_ONE_DAY,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check the active experiment.
item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "10 days remaining");
}
// Disable it and check it's previous experiment entry.
yield gExperiments.disableExperiment("experiment-3");
item = get_addon_element(gManagerWindow, "experiment-3");
Assert.ok(item, "Got add-on element.");
item.parentNode.ensureElementIsVisible(item);
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "5 days ago");
}
});
add_task(function* testDetailView() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
defineNow(gExperiments._policy, Date.now());
yield gExperiments._updateExperiments({
"version": 1,
"experiments": [
{
id: "experiment-4",
xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
xpiHash: "IRRELEVANT",
startTime: Date.now() / 1000 - 3600,
endTime: Date.now() / 1000 + 3600,
maxActiveSeconds: 600,
appName: [Services.appinfo.name],
channel: [gExperiments._policy.updatechannel()],
},
],
});
yield gExperiments._run();
// Check active experiment.
yield openDetailsView("test-experiment1@experiments.mozilla.org");
let el = gManagerWindow.document.getElementById("detail-experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Active");
}
el = gManagerWindow.document.getElementById("detail-experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Less than a day remaining");
}
el = gManagerWindow.document.getElementById("detail-version");
is_element_hidden(el, "detail-version should be hidden.");
el = gManagerWindow.document.getElementById("detail-creator");
is_element_hidden(el, "detail-creator should be hidden.");
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
// Check previous experiment.
yield gCategoryUtilities.openType("experiment");
yield openDetailsView("experiment-3");
el = gManagerWindow.document.getElementById("detail-experiment-state");
is_element_visible(el, "Experiment state label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "Complete");
}
el = gManagerWindow.document.getElementById("detail-experiment-time");
is_element_visible(el, "Experiment time label should be visible.");
if (gIsEnUsLocale) {
Assert.equal(el.value, "5 days ago");
}
el = gManagerWindow.document.getElementById("detail-version");
is_element_hidden(el, "detail-version should be hidden.");
el = gManagerWindow.document.getElementById("detail-creator");
is_element_hidden(el, "detail-creator should be hidden.");
el = gManagerWindow.document.getElementById("detail-experiment-bullet");
is_element_visible(el, "experiment-bullet should be visible.");
});
add_task(function* testRemoveAndUndo() {
if (!gExperiments) {
info("Skipping experiments test because that feature isn't available.");
return;
}
yield gCategoryUtilities.openType("experiment");
let addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(addon, "Got add-on element.");
yield clickRemoveButton(addon);
addon.parentNode.ensureElementIsVisible(addon);
let el = gManagerWindow.document.getAnonymousElementByAttribute(addon, "class", "pending");
is_element_visible(el, "Uninstall undo information should be visible.");
yield clickUndoButton(addon);
addon = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(addon, "Got add-on element.");
});
add_task(function* testCleanup() {
if (gExperiments) {
Services.prefs.clearUserPref("experiments.enabled");
Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI);
// We perform the uninit/init cycle to purge any leftover state.
yield OS.File.remove(gExperiments._cacheFilePath);
yield gExperiments.uninit();
yield gExperiments.init();
Services.prefs.clearUserPref("toolkit.telemetry.enabled");
}
// Check post-conditions.
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
yield close_manager(gManagerWindow);
});