Merge mozilla-inbound to mozilla-central. a=merge
authorAndreea Pavel <apavel@mozilla.com>
Mon, 16 Apr 2018 12:52:34 +0300
changeset 467361 6276ec7ebbf33e3484997b189f20fc1511534187
parent 466973 ceb3db3b31a07a2aaa164bdacb21f4135373aadb (current diff)
parent 467360 ac685df07bfc81d80f81e40d7cac55cbf56d84c1 (diff)
child 467362 6a87ef9c1acc5f6af848c00b8b9f608444ff8a72
child 467367 6c64530dd9240265d42c5e2c61ad94d875be01ae
child 467414 066fefe52d2eb0d4315a232a14b2d10fb554ab2d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
6276ec7ebbf3 / 61.0a1 / 20180416100103 / files
nightly linux64
6276ec7ebbf3 / 61.0a1 / 20180416100103 / files
nightly mac
6276ec7ebbf3 / 61.0a1 / 20180416100103 / files
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
Merge mozilla-inbound to mozilla-central. a=merge
browser/experiments/.eslintrc.js
browser/experiments/Experiments.jsm
browser/experiments/Experiments.manifest
browser/experiments/ExperimentsService.js
browser/experiments/Makefile.in
browser/experiments/docs/index.rst
browser/experiments/docs/manifest.rst
browser/experiments/moz.build
browser/experiments/test/addons/experiment-1/install.rdf
browser/experiments/test/addons/experiment-1a/install.rdf
browser/experiments/test/addons/experiment-2/install.rdf
browser/experiments/test/addons/experiment-racybranch/bootstrap.js
browser/experiments/test/addons/experiment-racybranch/install.rdf
browser/experiments/test/xpcshell/.eslintrc.js
browser/experiments/test/xpcshell/experiments_1.manifest
browser/experiments/test/xpcshell/head.js
browser/experiments/test/xpcshell/test_activate.js
browser/experiments/test/xpcshell/test_api.js
browser/experiments/test/xpcshell/test_cache.js
browser/experiments/test/xpcshell/test_cacherace.js
browser/experiments/test/xpcshell/test_conditions.js
browser/experiments/test/xpcshell/test_disableExperiments.js
browser/experiments/test/xpcshell/test_fetch.js
browser/experiments/test/xpcshell/test_nethang_bug1012924.js
browser/experiments/test/xpcshell/test_previous_provider.js
browser/experiments/test/xpcshell/test_telemetry.js
browser/experiments/test/xpcshell/test_telemetry_disabled.js
browser/experiments/test/xpcshell/test_upgrade.js
browser/experiments/test/xpcshell/xpcshell.ini
browser/extensions/presentation/bootstrap.js
browser/extensions/presentation/content/PresentationDevicePrompt.jsm
browser/extensions/presentation/moz.build
browser/extensions/presentation/skin/shared/link.svg
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/WebCryptoAPI/derive_bits_keys/hkdf.https.worker.html.ini
testing/web-platform/meta/bluetooth/idl/idl-Bluetooth.html.ini
testing/web-platform/meta/cookie-store/cookie_store_tests.tentative.html.ini
testing/web-platform/meta/cookie-store/cookie_store_tests.tentative.https.html.ini
testing/web-platform/meta/cookie-store/cookie_store_tests_static.tentative.html.ini
testing/web-platform/meta/cookie-store/cookie_store_tests_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/delete_cookies_static.tentative.html.ini
testing/web-platform/meta/cookie-store/delete_cookies_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/document_cookie_static.tentative.html.ini
testing/web-platform/meta/cookie-store/document_cookie_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/expiration.tentative.html.ini
testing/web-platform/meta/cookie-store/expiration.tentative.https.html.ini
testing/web-platform/meta/cookie-store/expiration_static.tentative.html.ini
testing/web-platform/meta/cookie-store/expiration_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/get_set_get_all_static.tentative.html.ini
testing/web-platform/meta/cookie-store/get_set_get_all_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/meta_http_equiv_set_cookie.tentative.html.ini
testing/web-platform/meta/cookie-store/meta_http_equiv_set_cookie.tentative.https.html.ini
testing/web-platform/meta/cookie-store/meta_http_equiv_set_cookie_static.tentative.html.ini
testing/web-platform/meta/cookie-store/meta_http_equiv_set_cookie_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/no_name_and_no_value_static.tentative.html.ini
testing/web-platform/meta/cookie-store/no_name_and_no_value_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/no_name_equals_in_value_static.tentative.html.ini
testing/web-platform/meta/cookie-store/no_name_equals_in_value_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/no_name_multiple_values_static.tentative.html.ini
testing/web-platform/meta/cookie-store/no_name_multiple_values_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/observation.tentative.html.ini
testing/web-platform/meta/cookie-store/observation.tentative.https.html.ini
testing/web-platform/meta/cookie-store/observation_static.tentative.html.ini
testing/web-platform/meta/cookie-store/observation_static.tentative.https.html.ini
testing/web-platform/meta/cookie-store/one_simple_origin_cookie.tentative.html.ini
testing/web-platform/meta/cookie-store/one_simple_origin_cookie.tentative.https.html.ini
testing/web-platform/meta/cookie-store/one_simple_origin_cookie_static.tentative.html.ini
testing/web-platform/meta/cookie-store/one_simple_origin_cookie_static.tentative.https.html.ini
testing/web-platform/meta/css/css-backgrounds/css3-background-size-001.html.ini
testing/web-platform/meta/css/css-text-decor/text-emphasis-style-001.html.ini
testing/web-platform/meta/css/css-text/i18n/css3-text-line-break-opclns-013.html.ini
testing/web-platform/meta/css/css-text/i18n/css3-text-line-break-opclns-048.html.ini
testing/web-platform/meta/css/css-text/i18n/css3-text-line-break-opclns-118.html.ini
testing/web-platform/meta/css/css-text/i18n/css3-text-line-break-opclns-154.html.ini
testing/web-platform/meta/css/css-text/line-break/line-break-normal-025.xht.ini
testing/web-platform/meta/css/css-text/line-break/line-break-strict-018a.xht.ini
testing/web-platform/meta/css/css-text/line-break/line-break-strict-018b.xht.ini
testing/web-platform/meta/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue.tentative.html.ini
testing/web-platform/meta/css/css-variables/variable-substitution-shadow-properties.html.ini
testing/web-platform/meta/css/cssom/index-002.html.ini
testing/web-platform/meta/css/cssom/inline-style-001.html.ini
testing/web-platform/meta/css/cssom/medialist-interfaces-002.html.ini
testing/web-platform/meta/css/cssom/medialist-interfaces-004.html.ini
testing/web-platform/meta/css/cssom/style-sheet-interfaces-001.html.ini
testing/web-platform/meta/html/browsers/offline/appcache/workers/appcache-worker.html.ini
testing/web-platform/meta/html/browsers/offline/application-cache-api/api_status_idle.html.ini
testing/web-platform/meta/html/browsers/offline/application-cache-api/api_status_uncached.html.ini
testing/web-platform/meta/html/browsers/offline/application-cache-api/api_swapcache_error.html.ini
testing/web-platform/meta/html/browsers/offline/application-cache-api/api_update.html.ini
testing/web-platform/meta/html/browsers/offline/application-cache-api/api_update_error.html.ini
testing/web-platform/meta/html/browsers/offline/introduction-4/event_cached.html.ini
testing/web-platform/meta/html/browsers/offline/introduction-4/event_checking.html.ini
testing/web-platform/meta/html/browsers/offline/introduction-4/event_noupdate.html.ini
testing/web-platform/meta/html/browsers/offline/introduction-4/event_progress.html.ini
testing/web-platform/meta/html/browsers/offline/manifest_url_check.html.ini
testing/web-platform/meta/html/browsers/the-window-object/security-window/window-security.html.ini
testing/web-platform/meta/html/browsers/the-window-object/window-properties.html.ini
testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/009.html.ini
testing/web-platform/meta/html/dom/interfaces.html.ini
testing/web-platform/meta/keyboard-lock/navigator-keyboardLock-two-parallel-requests.https.html.ini
testing/web-platform/meta/keyboard-lock/navigator-keyboardLock-two-sequential-requests.https.html.ini
testing/web-platform/meta/keyboard-lock/navigator-keyboardLock.https.html.ini
testing/web-platform/meta/keyboard-lock/navigator-keyboardUnlock.https.html.ini
testing/web-platform/meta/navigation-timing/nav2_idlharness.html.ini
testing/web-platform/meta/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html.ini
testing/web-platform/meta/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html.ini
testing/web-platform/meta/payment-request/payment-request-abort-method.https.html.ini
testing/web-platform/meta/payment-request/payment-request-canmakepayment-method.https.html.ini
testing/web-platform/meta/preload/onload-event.html.ini
testing/web-platform/meta/preload/single-download-preload.html.ini
testing/web-platform/meta/sensors/SensorErrorEvent-constructor.https.html.ini
testing/web-platform/meta/sensors/idlharness.https.html.ini
testing/web-platform/meta/staticrange/idlharness.html.ini
testing/web-platform/meta/webauthn/interfaces.https.html.ini
testing/web-platform/meta/webrtc/RTCRtpReceiver-getStats.html.ini
testing/web-platform/tests/IndexedDB/interfaces.html
testing/web-platform/tests/IndexedDB/interfaces.worker.js
testing/web-platform/tests/bluetooth/idl/idl-Bluetooth.html
testing/web-platform/tests/bluetooth/requestDevice/consumes-user-gesture.https.html
testing/web-platform/tests/conformance-checkers/html/elements/a/href/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/area/href/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/audio/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/base/href/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/blockquote/cite/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/button/formaction/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/del/cite/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/embed/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/form/action/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/iframe/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/img/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/input/type-image-formaction/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/input/type-image-src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/input/type-submit-formaction/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/input/type-url-value/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/ins/cite/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/link/href/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/object/data/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/q/cite/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/script/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/source/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/track/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/video/poster/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/elements/video/src/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/microdata/itemid/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/conformance-checkers/html/microdata/itemtype/scheme-javascript-no-slash-malformed-novalid.html
testing/web-platform/tests/cookie-store/cookie_store_tests.tentative.html
testing/web-platform/tests/cookie-store/cookie_store_tests.tentative.https.html
testing/web-platform/tests/cookie-store/cookie_store_tests_static.tentative.html
testing/web-platform/tests/cookie-store/cookie_store_tests_static.tentative.https.html
testing/web-platform/tests/cookie-store/delete_cookies_static.tentative.html
testing/web-platform/tests/cookie-store/delete_cookies_static.tentative.https.html
testing/web-platform/tests/cookie-store/document_cookie_static.tentative.html
testing/web-platform/tests/cookie-store/document_cookie_static.tentative.https.html
testing/web-platform/tests/cookie-store/expiration.tentative.html
testing/web-platform/tests/cookie-store/expiration.tentative.https.html
testing/web-platform/tests/cookie-store/expiration_static.tentative.html
testing/web-platform/tests/cookie-store/expiration_static.tentative.https.html
testing/web-platform/tests/cookie-store/get_set_get_all_static.tentative.html
testing/web-platform/tests/cookie-store/get_set_get_all_static.tentative.https.html
testing/web-platform/tests/cookie-store/meta_http_equiv_set_cookie.tentative.html
testing/web-platform/tests/cookie-store/meta_http_equiv_set_cookie.tentative.https.html
testing/web-platform/tests/cookie-store/meta_http_equiv_set_cookie_static.tentative.html
testing/web-platform/tests/cookie-store/meta_http_equiv_set_cookie_static.tentative.https.html
testing/web-platform/tests/cookie-store/no_name_and_no_value_static.tentative.html
testing/web-platform/tests/cookie-store/no_name_and_no_value_static.tentative.https.html
testing/web-platform/tests/cookie-store/no_name_equals_in_value_static.tentative.html
testing/web-platform/tests/cookie-store/no_name_equals_in_value_static.tentative.https.html
testing/web-platform/tests/cookie-store/no_name_multiple_values_static.tentative.html
testing/web-platform/tests/cookie-store/no_name_multiple_values_static.tentative.https.html
testing/web-platform/tests/cookie-store/observation.tentative.html
testing/web-platform/tests/cookie-store/observation.tentative.https.html
testing/web-platform/tests/cookie-store/observation_static.tentative.html
testing/web-platform/tests/cookie-store/observation_static.tentative.https.html
testing/web-platform/tests/cookie-store/one_simple_origin_cookie.tentative.html
testing/web-platform/tests/cookie-store/one_simple_origin_cookie.tentative.https.html
testing/web-platform/tests/cookie-store/one_simple_origin_cookie_static.tentative.html
testing/web-platform/tests/cookie-store/one_simple_origin_cookie_static.tentative.https.html
testing/web-platform/tests/cookie-store/resources/cookie-store-tests.js
testing/web-platform/tests/cookie-store/resources/testharness-helpers.js
testing/web-platform/tests/css/css-grid/support/grid.css
testing/web-platform/tests/css/css-position/position-sticky-nested-bottom-ref.html
testing/web-platform/tests/css/css-position/position-sticky-nested-left-ref.html
testing/web-platform/tests/css/css-position/position-sticky-nested-right-ref.html
testing/web-platform/tests/css/css-position/position-sticky-nested-top-ref.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-013.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-048.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-118.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-154.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-216.html
testing/web-platform/tests/css/css-text/i18n/css3-text-line-break-opclns-224.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-013-ref.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-048-ref.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-118-ref.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-154-ref.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-216-ref.html
testing/web-platform/tests/css/css-text/i18n/reference/css3-text-line-break-opclns-224-ref.html
testing/web-platform/tests/css/css-text/line-break/line-break-normal-025.xht
testing/web-platform/tests/css/css-text/line-break/line-break-strict-018a.xht
testing/web-platform/tests/css/css-text/line-break/line-break-strict-018b.xht
testing/web-platform/tests/css/css-text/line-break/reference/line-break-normal-025-ref.xht
testing/web-platform/tests/css/css-text/line-break/reference/line-break-strict-018a-ref.xht
testing/web-platform/tests/css/css-text/line-break/reference/line-break-strict-018b-ref.xht
testing/web-platform/tests/css/css-text/word-break/reference/word-break-normal-002-ref.xht
testing/web-platform/tests/css/css-text/word-break/word-break-normal-002.xht
testing/web-platform/tests/css/css-typed-om/stylevalue-subclasses/numeric-objects/cssUnitValue.tentative.html
testing/web-platform/tests/css/cssom/GetBoundingRect.html
testing/web-platform/tests/css/cssom/index-001.html
testing/web-platform/tests/css/cssom/index-002.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-32x32.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/aqua-yellow-37x37.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bl.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-bo.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-br.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ct.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-le.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-ri.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tl.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-to.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule-tr.png
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reticule.png
testing/web-platform/tests/html/browsers/offline/appcache/workers/appcache-worker.html
testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_idle.html
testing/web-platform/tests/html/browsers/offline/application-cache-api/api_status_uncached.html
testing/web-platform/tests/html/browsers/offline/application-cache-api/api_swapcache_error.html
testing/web-platform/tests/html/browsers/offline/application-cache-api/api_update.html
testing/web-platform/tests/html/browsers/offline/application-cache-api/api_update_error.html
testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_event-manual.html
testing/web-platform/tests/html/browsers/offline/browser-state/navigator_online_online.html
testing/web-platform/tests/html/browsers/offline/introduction-4/event_cached.html
testing/web-platform/tests/html/browsers/offline/introduction-4/event_checking.html
testing/web-platform/tests/html/browsers/offline/introduction-4/event_noupdate.html
testing/web-platform/tests/html/browsers/offline/introduction-4/event_progress.html
testing/web-platform/tests/html/browsers/offline/manifest_main_empty-manual.html
testing/web-platform/tests/html/browsers/offline/manifest_notchanged_online-manual.html
testing/web-platform/tests/html/browsers/offline/manifest_section_empty-manual.html
testing/web-platform/tests/html/browsers/offline/manifest_section_many-manual.html
testing/web-platform/tests/html/browsers/offline/manifest_url_check.html
testing/web-platform/tests/html/browsers/offline/no-appcache-in-shared-workers-historical.html
testing/web-platform/tests/html/browsers/offline/section_network_offline-manual.html
testing/web-platform/tests/html/browsers/offline/section_network_online-manual.html
testing/web-platform/tests/html/browsers/the-window-object/security-window/window-security.html
testing/web-platform/tests/html/browsers/the-window-object/window-properties.html
testing/web-platform/tests/html/dom/dynamic-markup-insertion/opening-the-input-stream/009.html
testing/web-platform/tests/html/dom/interfaces.html
testing/web-platform/tests/html/editing/focus/focus-01-manual.html
testing/web-platform/tests/html/editing/focus/focus-02-manual.html
testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/domstringlist.idl
testing/web-platform/tests/keyboard-lock/navigator-keyboardLock-two-parallel-requests.https.html
testing/web-platform/tests/keyboard-lock/navigator-keyboardLock-two-sequential-requests.https.html
testing/web-platform/tests/keyboard-lock/navigator-keyboardLock.https.html
testing/web-platform/tests/keyboard-lock/navigator-keyboardUnlock.https.html
testing/web-platform/tests/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html
testing/web-platform/tests/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html
testing/web-platform/tests/payment-request/payment-request-abort-method.https.html
testing/web-platform/tests/payment-request/payment-request-canmakepayment-method.https.html
testing/web-platform/tests/pointerevents/pointerevent_touch-action-pan-down-css_touch-manual.html
testing/web-platform/tests/pointerevents/pointerevent_touch-action-pan-left-css_touch-manual.html
testing/web-platform/tests/pointerevents/pointerevent_touch-action-pan-right-css_touch-manual.html
testing/web-platform/tests/pointerevents/pointerevent_touch-action-pan-up-css_touch-manual.html
testing/web-platform/tests/resource-timing/resource_frame_initiator_type.html
testing/web-platform/tests/sensors/OWNERS
testing/web-platform/tests/sensors/SensorErrorEvent-constructor.https.html
testing/web-platform/tests/sensors/generic-sensor-feature-policy-test.sub.js
testing/web-platform/tests/sensors/generic-sensor-tests.js
testing/web-platform/tests/sensors/idlharness.https.html
testing/web-platform/tests/staticrange/OWNERS
testing/web-platform/tests/staticrange/idlharness.html
testing/web-platform/tests/wasm/many-memories.window.js
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedTiming.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/idlharness.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html
testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html
testing/web-platform/tests/webauthn/interfaces.https.html
testing/web-platform/tests/webrtc/RTCRtpReceiver-getStats.html
toolkit/mozapps/extensions/test/addons/test_experiment1/bootstrap.js
toolkit/mozapps/extensions/test/addons/test_experiment1/install.rdf
toolkit/mozapps/extensions/test/browser/addons/browser_experiment1.xpi
toolkit/mozapps/extensions/test/browser/addons/browser_experiment1/install.rdf
toolkit/mozapps/extensions/test/browser/browser_experiments.js
toolkit/mozapps/extensions/test/xpcshell/test_experiment.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1505,23 +1505,16 @@ pref("toolkit.telemetry.firstShutdownPin
 pref("toolkit.telemetry.newProfilePing.enabled", true);
 // Enables sending 'update' pings on Firefox updates.
 pref("toolkit.telemetry.updatePing.enabled", true);
 // Enables sending 'bhr' pings when the browser hangs.
 pref("toolkit.telemetry.bhrPing.enabled", true);
 // Enables using Hybrid Content Telemetry from Mozilla privileged pages.
 pref("toolkit.telemetry.hybridContent.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);
-
 // Ping Centre Telemetry settings.
 pref("browser.ping-centre.telemetry", true);
 pref("browser.ping-centre.log", false);
 pref("browser.ping-centre.staging.endpoint", "https://onyx_tiles.stage.mozaws.net/v3/links/ping-centre");
 pref("browser.ping-centre.production.endpoint", "https://tiles.services.mozilla.com/v3/links/ping-centre");
 
 // Enable GMP support in the addon manager.
 pref("media.gmp-provider.enabled", true);
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1104,17 +1104,17 @@
                    openInTabs="children"
                    onmouseup="BookmarksEventHandler.onMouseUp(event);"
                    oncommand="BookmarksEventHandler.onCommand(event);"
                    onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                    onpopupshowing="BookmarkingUI.onPopupShowing(event);
                                    BookmarkingUI.attachPlacesView(event, this);"
                    tooltip="bhTooltip" popupsinherittooltip="true">
           <menuitem id="BMB_viewBookmarksSidebar"
-                    class="subviewbutton"
+                    class="menuitem-iconic subviewbutton"
                     label-show="&viewBookmarksSidebar2.label;"
                     label-hide="&hideBookmarksSidebar.label;"
                     oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
           <!-- NB: temporary solution for bug 985024, this should go away soon. -->
           <menuitem id="BMB_bookmarksShowAllTop"
                     class="menuitem-iconic subviewbutton"
                     label="&showAllBookmarks2.label;"
                     command="Browser:ShowAllBookmarks"
@@ -1126,17 +1126,17 @@
                 container="true">
             <menupopup id="BMB_bookmarksToolbarPopup"
                        placespopup="true"
                        context="placesContext"
                        onpopupshowing="if (!this.parentNode._placesView)
                                          new PlacesMenu(event, 'place:folder=TOOLBAR',
                                                         PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);">
               <menuitem id="BMB_viewBookmarksToolbar"
-                        class="subviewbutton"
+                        class="menuitem-iconic subviewbutton"
                         label-show="&viewBookmarksToolbar.label;"
                         label-hide="&hideBookmarksToolbar.label;"
                         oncommand="BookmarkingUI.toggleBookmarksToolbar();"/>
               <menuseparator/>
               <!-- Bookmarks toolbar items -->
             </menupopup>
           </menu>
           <menu id="BMB_unsortedBookmarks"
deleted file mode 100644
--- a/browser/experiments/.eslintrc.js
+++ /dev/null
@@ -1,10 +0,0 @@
-"use strict";
-
-module.exports = {
-  "rules": {
-    "no-unused-vars": ["error", {
-      "args": "none",
-      "vars": "all"
-    }]
-  }
-};
deleted file mode 100644
--- a/browser/experiments/Experiments.jsm
+++ /dev/null
@@ -1,2339 +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";
-
-var EXPORTED_SYMBOLS = [
-  "Experiments",
-];
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/osfile.jsm");
-ChromeUtils.import("resource://gre/modules/Log.jsm");
-ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm");
-
-Cu.importGlobalProperties(["XMLHttpRequest"]);
-
-ChromeUtils.defineModuleGetter(this, "UpdateUtils",
-                               "resource://gre/modules/UpdateUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonManager",
-                               "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
-                               "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
-                               "resource://gre/modules/TelemetryEnvironment.jsm");
-ChromeUtils.defineModuleGetter(this, "TelemetryLog",
-                               "resource://gre/modules/TelemetryLog.jsm");
-ChromeUtils.defineModuleGetter(this, "CommonUtils",
-                               "resource://services-common/utils.js");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
-                                   "@mozilla.org/xre/app-info;1",
-                                   "nsICrashReporter");
-
-const FILE_CACHE                = "experiments.json";
-const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
-const PREF_CHANGED_TOPIC        = "nsPref:changed";
-const MANIFEST_VERSION          = 1;
-const CACHE_VERSION             = 1;
-
-const KEEP_HISTORY_N_DAYS       = 180;
-
-const PREF_BRANCH               = "experiments.";
-const PREF_ENABLED              = "enabled"; // experiments.enabled
-const PREF_ACTIVE_EXPERIMENT    = "activeExperiment"; // whether we have an active experiment
-const PREF_LOGGING              = "logging";
-const PREF_LOGGING_LEVEL        = PREF_LOGGING + ".level"; // experiments.logging.level
-const PREF_LOGGING_DUMP         = PREF_LOGGING + ".dump"; // experiments.logging.dump
-const PREF_MANIFEST_URI         = "manifest.uri"; // experiments.logging.manifest.uri
-const PREF_FORCE_SAMPLE         = "force-sample-value"; // experiments.force-sample-value
-
-const URI_EXTENSION_STRINGS     = "chrome://mozapps/locale/extensions/extensions.properties";
-
-const CACHE_WRITE_RETRY_DELAY_SEC = 60 * 3;
-const MANIFEST_FETCH_TIMEOUT_MSEC = 60 * 3 * 1000; // 3 minutes
-
-const TELEMETRY_LOG = {
-  // log(key, [kind, experimentId, details])
-  ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
-  ACTIVATION: {
-    // Successfully activated.
-    ACTIVATED: "ACTIVATED",
-    // Failed to install the add-on.
-    INSTALL_FAILURE: "INSTALL_FAILURE",
-    // Experiment does not meet activation requirements. Details will
-    // be provided.
-    REJECTED: "REJECTED",
-  },
-
-  // log(key, [kind, experimentId, optionalDetails...])
-  TERMINATION_KEY: "EXPERIMENT_TERMINATION",
-  TERMINATION: {
-    // The Experiments service was disabled.
-    SERVICE_DISABLED: "SERVICE_DISABLED",
-    // Add-on uninstalled.
-    ADDON_UNINSTALLED: "ADDON_UNINSTALLED",
-    // The experiment disabled itself.
-    FROM_API: "FROM_API",
-    // The experiment expired (e.g. by exceeding the end date).
-    EXPIRED: "EXPIRED",
-    // Disabled after re-evaluating conditions. If this is specified,
-    // details will be provided.
-    RECHECK: "RECHECK",
-  },
-};
-XPCOMUtils.defineConstant(this, "TELEMETRY_LOG", TELEMETRY_LOG);
-
-const gPrefs = Services.prefs.getBranch(PREF_BRANCH);
-var gExperimentsEnabled = false;
-var gAddonProvider = null;
-var gExperiments = null;
-var gLogAppenderDump = null;
-var gPolicyCounter = 0;
-var gExperimentsCounter = 0;
-var gExperimentEntryCounter = 0;
-var gPreviousProviderCounter = 0;
-
-// Tracks active AddonInstall we know about so we can deny external
-// installs.
-var gActiveInstallURLs = new Set();
-
-// Tracks add-on IDs that are being uninstalled by us. This allows us
-// to differentiate between expected uninstalled and user-driven uninstalls.
-var gActiveUninstallAddonIDs = new Set();
-
-var gLogger;
-var gLogDumping = false;
-
-function configureLogging() {
-  if (!gLogger) {
-    gLogger = Log.repository.getLogger("Browser.Experiments");
-    gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
-  }
-  gLogger.level = gPrefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn);
-
-  let logDumping = gPrefs.getBoolPref(PREF_LOGGING_DUMP, false);
-  if (logDumping != gLogDumping) {
-    if (logDumping) {
-      gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
-      gLogger.addAppender(gLogAppenderDump);
-    } else {
-      gLogger.removeAppender(gLogAppenderDump);
-      gLogAppenderDump = null;
-    }
-    gLogDumping = logDumping;
-  }
-}
-
-// Loads a JSON file using OS.file. file is a string representing the path
-// of the file to be read, options contains additional options to pass to
-// OS.File.read.
-// Returns a Promise resolved with the json payload or rejected with
-// OS.File.Error or JSON.parse() errors.
-function loadJSONAsync(file, options) {
-  return (async function() {
-    let rawData = await OS.File.read(file, options);
-    // Read json file into a string
-    let data;
-    try {
-      // Obtain a converter to read from a UTF-8 encoded input stream.
-      let converter = new TextDecoder();
-      data = JSON.parse(converter.decode(rawData));
-    } catch (ex) {
-      gLogger.error("Experiments: Could not parse JSON: " + file + " " + ex);
-      throw ex;
-    }
-    return data;
-  })();
-}
-
-// Returns a promise that is resolved with the AddonInstall for that URL.
-function addonInstallForURL(url, hash) {
-  return AddonManager.getInstallForURL(url, null, "application/x-xpinstall", hash);
-}
-
-// Returns a promise that is resolved with an Array<Addon> of the installed
-// experiment addons.
-function installedExperimentAddons() {
-  return AddonManager.getActiveAddons(["experiment"]).then(({addons}) => {
-    return addons.filter(a => !a.appDisabled);
-  });
-}
-
-// Takes an Array<Addon> and returns a promise that is resolved when the
-// addons are uninstalled.
-async function uninstallAddons(addons) {
-  if (!AddonManagerPrivate.isDBLoaded()) {
-    await new Promise(resolve => {
-      Services.obs.addObserver({
-        observe(subject, topic, data) {
-          Services.obs.removeObserver(this, "xpi-database-loaded");
-          resolve();
-        },
-      }, "xpi-database-loaded");
-    });
-
-    // This function was called during startup so the addons that were
-    // passed in were partial addon objects.  Now that the full addons
-    // database is loaded, get proper Addon objects.
-    addons = await AddonManager.getAddonsByIDs(addons.map(a => a.id));
-  }
-
-  let ids = new Set(addons.map(addon => addon.id));
-  return new Promise(resolve => {
-
-    let listener = {};
-    listener.onUninstalled = addon => {
-      if (!ids.has(addon.id)) {
-        return;
-      }
-
-      ids.delete(addon.id);
-      if (ids.size == 0) {
-        AddonManager.removeAddonListener(listener);
-        resolve();
-      }
-    };
-
-    AddonManager.addAddonListener(listener);
-
-    for (let addon of addons) {
-      addon.uninstall();
-    }
-
-  });
-}
-
-/**
- * The experiments module.
- */
-
-var Experiments = {
-  /**
-   * Provides access to the global `Experiments.Experiments` instance.
-   */
-  instance() {
-    if (!gExperiments) {
-      gExperiments = new Experiments.Experiments();
-    }
-
-    return gExperiments;
-  },
-};
-
-/*
- * The policy object allows us to inject fake enviroment data from the
- * outside by monkey-patching.
- */
-
-Experiments.Policy = function() {
-  this._log = Log.repository.getLoggerWithMessagePrefix(
-    "Browser.Experiments.Policy",
-    "Policy #" + gPolicyCounter++ + "::");
-
-  // Set to true to ignore hash verification on downloaded XPIs. This should
-  // not be used outside of testing.
-  this.ignoreHashes = false;
-};
-
-Experiments.Policy.prototype = {
-  now() {
-    return new Date();
-  },
-
-  random() {
-    let pref = gPrefs.getStringPref(PREF_FORCE_SAMPLE, undefined);
-    if (pref !== undefined) {
-      let val = Number.parseFloat(pref);
-      this._log.debug("random sample forced: " + val);
-      if (isNaN(val) || val < 0) {
-        return 0;
-      }
-      if (val > 1) {
-        return 1;
-      }
-      return val;
-    }
-    return Math.random();
-  },
-
-  futureDate(offset) {
-    return new Date(this.now().getTime() + offset);
-  },
-
-  oneshotTimer(callback, timeout, thisObj, name) {
-    return CommonUtils.namedTimer(callback, timeout, thisObj, name);
-  },
-
-  updatechannel() {
-    return UpdateUtils.UpdateChannel;
-  },
-
-  locale() {
-    return Services.locale.getAppLocaleAsLangTag();
-  },
-
-  /**
-   * For testing a race condition, one of the tests delays the callback of
-   * writing the cache by replacing this policy function.
-   */
-  delayCacheWrite(promise) {
-    return promise;
-  },
-};
-
-function AlreadyShutdownError(message = "already shut down") {
-  Error.call(this, message);
-  let error = new Error();
-  this.name = "AlreadyShutdownError";
-  this.message = message;
-  this.stack = error.stack;
-}
-AlreadyShutdownError.prototype = Object.create(Error.prototype);
-AlreadyShutdownError.prototype.constructor = AlreadyShutdownError;
-
-function CacheWriteError(message = "Error writing cache file") {
-  Error.call(this, message);
-  let error = new Error();
-  this.name = "CacheWriteError";
-  this.message = message;
-  this.stack = error.stack;
-}
-CacheWriteError.prototype = Object.create(Error.prototype);
-CacheWriteError.prototype.constructor = CacheWriteError;
-
-/**
- * Manages the experiments and provides an interface to control them.
- */
-
-Experiments.Experiments = function(policy = new Experiments.Policy()) {
-  let log = Log.repository.getLoggerWithMessagePrefix(
-      "Browser.Experiments.Experiments",
-      "Experiments #" + gExperimentsCounter++ + "::");
-
-  // At the time of this writing, Experiments.jsm has severe
-  // crashes. For forensics purposes, keep the last few log
-  // messages in memory and upload them in case of crash.
-  this._forensicsLogs = [];
-  this._forensicsLogs.length = 30;
-  this._log = Object.create(log);
-  this._log.log = (level, string, params) => {
-    this._addToForensicsLog("Experiments", string);
-    log.log(level, string, params);
-  };
-
-  this._log.trace("constructor");
-
-  // Capture the latest error, for forensics purposes.
-  this._latestError = null;
-
-
-  this._policy = policy;
-
-  // This is a Map of (string -> ExperimentEntry), keyed with the experiment id.
-  // It holds both the current experiments and history.
-  // Map() preserves insertion order, which means we preserve the manifest order.
-  // This is null until we've successfully completed loading the cache from
-  // disk the first time.
-  this._experiments = null;
-  this._refresh = false;
-  this._terminateReason = null; // or TELEMETRY_LOG.TERMINATION....
-  this._dirty = false;
-
-  // Loading the cache happens once asynchronously on startup
-  this._loadTask = null;
-
-  // The _main task handles all other actions:
-  // * refreshing the manifest off the network (if _refresh)
-  // * disabling/enabling experiments
-  // * saving the cache (if _dirty)
-  this._mainTask = null;
-
-  // Timer for re-evaluating experiment status.
-  this._timer = null;
-
-  this._shutdown = false;
-  this._networkRequest = null;
-
-  // We need to tell when we first evaluated the experiments to fire an
-  // experiments-changed notification when we only loaded completed experiments.
-  this._firstEvaluate = true;
-
-  this.init();
-};
-
-Experiments.Experiments.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver, Ci.nsISupportsWeakReference]),
-
-  /**
-   * `true` if the experiments manager is currently setup (has been fully initialized
-   * and not uninitialized yet).
-   */
-  get isReady() {
-    return !this._shutdown;
-  },
-
-  observe(subject, topic, data) {
-    switch (topic) {
-      case PREF_CHANGED_TOPIC:
-        if (data == PREF_BRANCH + PREF_MANIFEST_URI) {
-          this.updateManifest();
-        } else if (data == PREF_BRANCH + PREF_ENABLED) {
-          this._toggleExperimentsEnabled(gPrefs.getBoolPref(PREF_ENABLED, false));
-        }
-        break;
-    }
-  },
-
-  init() {
-    this._shutdown = false;
-    configureLogging();
-
-    gExperimentsEnabled = gPrefs.getBoolPref(PREF_ENABLED, false) && Services.telemetry.canRecordExtended;
-    this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled);
-
-    Services.prefs.addObserver(PREF_BRANCH + PREF_LOGGING, configureLogging);
-    Services.prefs.addObserver(PREF_BRANCH + PREF_MANIFEST_URI, this, true);
-    Services.prefs.addObserver(PREF_BRANCH + PREF_ENABLED, this, true);
-
-    AddonManager.shutdown.addBlocker("Experiments.jsm shutdown",
-      this.uninit.bind(this),
-      this._getState.bind(this)
-    );
-
-    this._registerWithAddonManager();
-
-    this._loadTask = this._loadFromCache();
-
-    return this._loadTask.then(
-      () => {
-        this._log.trace("_loadTask finished ok");
-        this._loadTask = null;
-        return this._run();
-      },
-      (e) => {
-        this._log.error("_loadFromCache caught error: " + e);
-        this._latestError = e;
-        throw e;
-      }
-    );
-  },
-
-  /**
-   * Uninitialize this instance.
-   *
-   * This function is susceptible to race conditions. If it is called multiple
-   * times before the previous uninit() has completed or if it is called while
-   * an init() operation is being performed, the object may get in bad state
-   * and/or deadlock could occur.
-   *
-   * @return Promise<>
-   *         The promise is fulfilled when all pending tasks are finished.
-   */
-  async uninit() {
-    this._log.trace("uninit: started");
-    await this._loadTask;
-    this._log.trace("uninit: finished with _loadTask");
-
-    if (!this._shutdown) {
-      this._log.trace("uninit: no previous shutdown");
-      this._unregisterWithAddonManager();
-
-      Services.prefs.removeObserver(PREF_BRANCH + PREF_LOGGING, configureLogging);
-      Services.prefs.removeObserver(PREF_BRANCH + PREF_MANIFEST_URI, this);
-      Services.prefs.removeObserver(PREF_BRANCH + PREF_ENABLED, this);
-
-      if (this._timer) {
-        this._timer.clear();
-      }
-    }
-
-    this._shutdown = true;
-    if (this._mainTask) {
-      if (this._networkRequest) {
-        try {
-          this._log.trace("Aborting pending network request: " + this._networkRequest);
-          this._networkRequest.abort();
-        } catch (e) {
-          // pass
-        }
-      }
-      try {
-        this._log.trace("uninit: waiting on _mainTask");
-        await this._mainTask;
-      } catch (e) {
-        // We error out of tasks after shutdown via this exception.
-        this._log.trace(`uninit: caught error - ${e}`);
-        if (!(e instanceof AlreadyShutdownError)) {
-          this._latestError = e;
-          throw e;
-        }
-      }
-    }
-
-    this._log.info("Completed uninitialization.");
-  },
-
-  // Return state information, for debugging purposes.
-  _getState() {
-    let activeExperiment = this._getActiveExperiment();
-    let state = {
-      isShutdown: this._shutdown,
-      isEnabled: gExperimentsEnabled,
-      isRefresh: this._refresh,
-      isDirty: this._dirty,
-      isFirstEvaluate: this._firstEvaluate,
-      hasLoadTask: !!this._loadTask,
-      hasMainTask: !!this._mainTask,
-      hasTimer: !!this._hasTimer,
-      hasAddonProvider: !!gAddonProvider,
-      latestLogs: this._forensicsLogs,
-      experiments: this._experiments ? [...this._experiments.keys()] : null,
-      terminateReason: this._terminateReason,
-      activeExperiment: activeExperiment ? activeExperiment.id : null,
-    };
-    if (this._latestError) {
-      if (typeof this._latestError == "object") {
-        state.latestError = {
-          message: this._latestError.message,
-          stack: this._latestError.stack
-        };
-      } else {
-        state.latestError = "" + this._latestError;
-      }
-    }
-    return state;
-  },
-
-  _addToForensicsLog(what, string) {
-    this._forensicsLogs.shift();
-    let timeInSec = Math.floor(Services.telemetry.msSinceProcessStart() / 1000);
-    this._forensicsLogs.push(`${timeInSec}: ${what} - ${string}`);
-  },
-
-  _registerWithAddonManager(previousExperimentsProvider) {
-    this._log.trace("Registering instance with Addon Manager.");
-
-    AddonManager.addAddonListener(this);
-    AddonManager.addInstallListener(this);
-
-    if (!gAddonProvider) {
-      // The properties of this AddonType should be kept in sync with the
-      // experiment AddonType registered in XPIProvider.
-      this._log.trace("Registering previous experiment add-on provider.");
-      gAddonProvider = previousExperimentsProvider || new Experiments.PreviousExperimentProvider(this);
-      AddonManagerPrivate.registerProvider(gAddonProvider, [
-          new AddonManagerPrivate.AddonType("experiment",
-                                            URI_EXTENSION_STRINGS,
-                                            "type.experiment.name",
-                                            AddonManager.VIEW_TYPE_LIST,
-                                            11000,
-                                            AddonManager.TYPE_UI_HIDE_EMPTY),
-      ]);
-    }
-
-  },
-
-  _unregisterWithAddonManager() {
-    this._log.trace("Unregistering instance with Addon Manager.");
-
-    this._log.trace("Removing install listener from add-on manager.");
-    AddonManager.removeInstallListener(this);
-    this._log.trace("Removing addon listener from add-on manager.");
-    AddonManager.removeAddonListener(this);
-    this._log.trace("Finished unregistering with addon manager.");
-
-    if (gAddonProvider) {
-      this._log.trace("Unregistering previous experiment add-on provider.");
-      AddonManagerPrivate.unregisterProvider(gAddonProvider);
-      gAddonProvider = null;
-    }
-  },
-
-  /*
-   * Change the PreviousExperimentsProvider that this instance uses.
-   * For testing only.
-   */
-  _setPreviousExperimentsProvider(provider) {
-    this._unregisterWithAddonManager();
-    this._registerWithAddonManager(provider);
-  },
-
-  /**
-   * Throws an exception if we've already shut down.
-   */
-  _checkForShutdown() {
-    if (this._shutdown) {
-      throw new AlreadyShutdownError("uninit() already called");
-    }
-  },
-
-  /**
-   * Whether the experiments feature is enabled.
-   */
-  get enabled() {
-    return gExperimentsEnabled;
-  },
-
-  /**
-   * Toggle whether the experiments feature is enabled or not.
-   */
-  set enabled(enabled) {
-    this._log.trace("set enabled(" + enabled + ")");
-    gPrefs.setBoolPref(PREF_ENABLED, enabled);
-  },
-
-  async _toggleExperimentsEnabled(enabled) {
-    this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
-    let wasEnabled = gExperimentsEnabled;
-    gExperimentsEnabled = enabled && Services.telemetry.canRecordExtended;
-
-    if (wasEnabled == gExperimentsEnabled) {
-      return;
-    }
-
-    if (gExperimentsEnabled) {
-      await this.updateManifest();
-    } else {
-      await this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED);
-      if (this._timer) {
-        this._timer.clear();
-      }
-    }
-  },
-
-  /**
-   * Returns a promise that is resolved with an array of `ExperimentInfo` objects,
-   * which provide info on the currently and recently active experiments.
-   * The array is in chronological order.
-   *
-   * The experiment info is of the form:
-   * {
-   *   id: <string>,
-   *   name: <string>,
-   *   description: <string>,
-   *   active: <boolean>,
-   *   endDate: <integer>, // epoch ms
-   *   detailURL: <string>,
-   *   ... // possibly extended later
-   * }
-   *
-   * @return Promise<Array<ExperimentInfo>> Array of experiment info objects.
-   */
-  getExperiments() {
-    return (async () => {
-      await this._loadTask;
-      let list = [];
-
-      for (let [id, experiment] of this._experiments) {
-        if (!experiment.startDate) {
-          // We only collect experiments that are or were active.
-          continue;
-        }
-
-        list.push({
-          id,
-          name: experiment._name,
-          description: experiment._description,
-          active: experiment.enabled,
-          endDate: experiment.endDate.getTime(),
-          detailURL: experiment._homepageURL,
-          branch: experiment.branch,
-        });
-      }
-
-      // Sort chronologically, descending.
-      list.sort((a, b) => b.endDate - a.endDate);
-      return list;
-    })();
-  },
-
-  /**
-   * Returns the ExperimentInfo for the active experiment, or null
-   * if there is none.
-   */
-  getActiveExperiment() {
-    let experiment = this._getActiveExperiment();
-    if (!experiment) {
-      return null;
-    }
-
-    let info = {
-      id: experiment.id,
-      name: experiment._name,
-      description: experiment._description,
-      active: experiment.enabled,
-      endDate: experiment.endDate.getTime(),
-      detailURL: experiment._homepageURL,
-    };
-
-    return info;
-  },
-
-  /**
-   * Experiment "branch" support. If an experiment has multiple branches, it
-   * can record the branch with the experiment system and it will
-   * automatically be included in data reporting (FHR/telemetry payloads).
-   */
-
-  /**
-   * Set the experiment branch for the specified experiment ID.
-   * @returns Promise<>
-   */
-  async setExperimentBranch(id, branchstr) {
-    await this._loadTask;
-    let e = this._experiments.get(id);
-    if (!e) {
-      throw new Error("Experiment not found");
-    }
-    e.branch = String(branchstr);
-    this._log.trace("setExperimentBranch(" + id + ", " + e.branch + ") _dirty=" + this._dirty);
-    this._dirty = true;
-    Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC);
-    await this._run();
-  },
-  /**
-   * Get the branch of the specified experiment. If the experiment is unknown,
-   * throws an error.
-   *
-   * @param id The ID of the experiment. Pass null for the currently running
-   *           experiment.
-   * @returns Promise<string|null>
-   * @throws Error if the specified experiment ID is unknown, or if there is no
-   *         current experiment.
-   */
-  async getExperimentBranch(id = null) {
-    await this._loadTask;
-    let e;
-    if (id) {
-      e = this._experiments.get(id);
-      if (!e) {
-        throw new Error("Experiment not found");
-      }
-    } else {
-      e = this._getActiveExperiment();
-      if (e === null) {
-        throw new Error("No active experiment");
-      }
-    }
-    return e.branch;
-  },
-
-  /**
-   * Determine whether another date has the same UTC day as now().
-   */
-  _dateIsTodayUTC(d) {
-    let now = this._policy.now();
-
-    return stripDateToMidnight(now).getTime() == stripDateToMidnight(d).getTime();
-  },
-
-  /**
-   * Obtain the entry of the most recent active experiment that was active
-   * today.
-   *
-   * If no experiment was active today, this resolves to nothing.
-   *
-   * Assumption: Only a single experiment can be active at a time.
-   *
-   * @return Promise<object>
-   */
-  lastActiveToday() {
-    return (async () => {
-      let experiments = await this.getExperiments();
-
-      // Assumption: Ordered chronologically, descending, with active always
-      // first.
-      for (let experiment of experiments) {
-        if (experiment.active) {
-          return experiment;
-        }
-
-        if (experiment.endDate && this._dateIsTodayUTC(experiment.endDate)) {
-          return experiment;
-        }
-      }
-      return null;
-    })();
-  },
-
-  _run() {
-    this._log.trace("_run");
-    this._checkForShutdown();
-    if (!this._mainTask) {
-      this._mainTask = (async () => {
-        try {
-          await this._main();
-        } catch (e) {
-          // In the CacheWriteError case we want to reschedule
-          if (!(e instanceof CacheWriteError)) {
-            this._log.error("_main caught error: " + e);
-            return;
-          }
-        } finally {
-          this._mainTask = null;
-        }
-        this._log.trace("_main finished, scheduling next run");
-        try {
-          await this._scheduleNextRun();
-        } catch (ex) {
-          // We error out of tasks after shutdown via this exception.
-          if (!(ex instanceof AlreadyShutdownError)) {
-            throw ex;
-          }
-        }
-      })();
-    }
-    return this._mainTask;
-  },
-
-  async _main() {
-    do {
-      this._log.trace("_main iteration");
-      await this._loadTask;
-      if (!gExperimentsEnabled) {
-        this._refresh = false;
-      }
-
-      if (this._refresh) {
-        await this._loadManifest();
-      }
-      await this._evaluateExperiments();
-      if (this._dirty) {
-        await this._saveToCache();
-      }
-      // If somebody called .updateManifest() or disableExperiment()
-      // while we were running, go again right now.
-    }
-    while (this._refresh || this._terminateReason || this._dirty);
-  },
-
-  async _loadManifest() {
-    this._log.trace("_loadManifest");
-    let uri = Services.urlFormatter.formatURLPref(PREF_BRANCH + PREF_MANIFEST_URI);
-
-    this._checkForShutdown();
-
-    this._refresh = false;
-    try {
-      let responseText = await this._httpGetRequest(uri);
-      this._log.trace("_loadManifest() - responseText=\"" + responseText + "\"");
-
-      if (this._shutdown) {
-        return;
-      }
-
-      let data = JSON.parse(responseText);
-      this._updateExperiments(data);
-    } catch (e) {
-      this._log.error("_loadManifest - failure to fetch/parse manifest (continuing anyway): " + e);
-    }
-  },
-
-  /**
-   * Fetch an updated list of experiments and trigger experiment updates.
-   * Do only use when experiments are enabled.
-   *
-   * @return Promise<>
-   *         The promise is resolved when the manifest and experiment list is updated.
-   */
-  updateManifest() {
-    this._log.trace("updateManifest()");
-
-    if (!gExperimentsEnabled) {
-      return Promise.reject(new Error("experiments are disabled"));
-    }
-
-    if (this._shutdown) {
-      return Promise.reject(Error("uninit() alrady called"));
-    }
-
-    this._refresh = true;
-    return this._run();
-  },
-
-  notify(timer) {
-    this._log.trace("notify()");
-    this._checkForShutdown();
-    return this._run();
-  },
-
-  // START OF ADD-ON LISTENERS
-
-  onUninstalled(addon) {
-    this._log.trace("onUninstalled() - addon id: " + addon.id);
-    if (gActiveUninstallAddonIDs.has(addon.id)) {
-      this._log.trace("matches pending uninstall");
-      return;
-    }
-    let activeExperiment = this._getActiveExperiment();
-    if (!activeExperiment || activeExperiment._addonId != addon.id) {
-      return;
-    }
-
-    this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED);
-  },
-
-  /**
-   * @returns {Boolean} returns false when we cancel the install.
-   */
-  onInstallStarted(install) {
-    if (install.addon.type != "experiment") {
-      return true;
-    }
-
-    this._log.trace("onInstallStarted() - " + install.addon.id);
-    if (install.addon.appDisabled) {
-      // This is a PreviousExperiment
-      return true;
-    }
-
-    // We want to be in control of all experiment add-ons: reject installs
-    // for add-ons that we don't know about.
-
-    // We have a race condition of sorts to worry about here. We have 2
-    // onInstallStarted listeners. This one (the global one) and the one
-    // created as part of ExperimentEntry._installAddon. Because of the order
-    // they are registered in, this one likely executes first. Unfortunately,
-    // this means that the add-on ID is not yet set on the ExperimentEntry.
-    // So, we can't just look at this._trackedAddonIds because the new experiment
-    // will have its add-on ID set to null. We work around this by storing a
-    // identifying field - the source URL of the install - in a module-level
-    // variable (so multiple Experiments instances doesn't cancel each other
-    // out).
-
-    if (this._trackedAddonIds.has(install.addon.id)) {
-      this._log.info("onInstallStarted allowing install because add-on ID " +
-                     "tracked by us.");
-      return true;
-    }
-
-    if (gActiveInstallURLs.has(install.sourceURI.spec)) {
-      this._log.info("onInstallStarted allowing install because install " +
-                     "tracked by us.");
-      return true;
-    }
-
-    this._log.warn("onInstallStarted cancelling install of unknown " +
-                   "experiment add-on: " + install.addon.id);
-    return false;
-  },
-
-  // END OF ADD-ON LISTENERS.
-
-  _getExperimentByAddonId(addonId) {
-    for (let [, entry] of this._experiments) {
-      if (entry._addonId === addonId) {
-        return entry;
-      }
-    }
-
-    return null;
-  },
-
-  /*
-   * Helper function to make HTTP GET requests. Returns a promise that is resolved with
-   * the responseText when the request is complete.
-   */
-  _httpGetRequest(url) {
-    this._log.trace("httpGetRequest(" + url + ")");
-    let xhr = new XMLHttpRequest();
-
-    this._networkRequest = xhr;
-    return new Promise((resolve, reject) => {
-
-      let log = this._log;
-      let errorhandler = (evt) => {
-        log.error("httpGetRequest::onError() - Error making request to " + url + ": " + evt.type);
-        reject(new Error("Experiments - XHR error for " + url + " - " + evt.type));
-        this._networkRequest = null;
-      };
-      xhr.onerror = errorhandler;
-      xhr.ontimeout = errorhandler;
-      xhr.onabort = errorhandler;
-
-      xhr.onload = (event) => {
-        if (xhr.status !== 200 && xhr.state !== 0) {
-          log.error("httpGetRequest::onLoad() - Request to " + url + " returned status " + xhr.status);
-          reject(new Error("Experiments - XHR status for " + url + " is " + xhr.status));
-          this._networkRequest = null;
-          return;
-        }
-
-        resolve(xhr.responseText);
-        this._networkRequest = null;
-      };
-
-      try {
-        xhr.open("GET", url);
-
-        if (xhr.channel instanceof Ci.nsISupportsPriority) {
-          xhr.channel.priority = Ci.nsISupportsPriority.PRIORITY_LOWEST;
-        }
-
-        xhr.timeout = MANIFEST_FETCH_TIMEOUT_MSEC;
-        xhr.send(null);
-      } catch (e) {
-        this._log.error("httpGetRequest() - Error opening request to " + url + ": " + e);
-        reject(new Error("Experiments - Error opening XHR for " + url));
-      }
-    });
-  },
-
-  /*
-   * Path of the cache file we use in the profile.
-   */
-  get _cacheFilePath() {
-    return OS.Path.join(OS.Constants.Path.profileDir, FILE_CACHE);
-  },
-
-  /*
-   * Part of the main task to save the cache to disk, called from _main.
-   */
-  async _saveToCache() {
-    this._log.trace("_saveToCache");
-    let path = this._cacheFilePath;
-    this._dirty = false;
-    try {
-      let textData = JSON.stringify({
-        version: CACHE_VERSION,
-        data: [...this._experiments.values()].map(e => e.toJSON()),
-      });
-
-      let encoder = new TextEncoder();
-      let data = encoder.encode(textData);
-      let options = { tmpPath: path + ".tmp", compression: "lz4" };
-      await this._policy.delayCacheWrite(OS.File.writeAtomic(path, data, options));
-    } catch (e) {
-      // We failed to write the cache, it's still dirty.
-      this._dirty = true;
-      this._log.error("_saveToCache failed and caught error: " + e);
-      throw new CacheWriteError();
-    }
-
-    this._log.debug("_saveToCache saved to " + path);
-  },
-
-  /*
-   * Task function, load the cached experiments manifest file from disk.
-   */
-  async _loadFromCache() {
-    this._log.trace("_loadFromCache");
-    let path = this._cacheFilePath;
-    try {
-      let result = await loadJSONAsync(path, { compression: "lz4" });
-      this._populateFromCache(result);
-    } catch (e) {
-      this._experiments = new Map();
-      if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
-        // No cached manifest yet.
-        this._log.trace("_loadFromCache - no cached manifest yet");
-      } else {
-        this._log.error("_loadFromCache - caught error", e);
-      }
-    }
-  },
-
-  _populateFromCache(data) {
-    this._log.trace("populateFromCache() - data: " + JSON.stringify(data));
-
-    // If the user has a newer cache version than we can understand, we fail
-    // hard; no experiments should be active in this older client.
-    if (CACHE_VERSION !== data.version) {
-      throw new Error("Experiments::_populateFromCache() - invalid cache version");
-    }
-
-    let experiments = new Map();
-    for (let item of data.data) {
-      let entry = new Experiments.ExperimentEntry(this._policy);
-      if (!entry.initFromCacheData(item)) {
-        continue;
-      }
-
-      // Discard old experiments if they ended more than 180 days ago.
-      if (entry.shouldDiscard()) {
-        // We discarded an experiment, the cache needs to be updated.
-        this._dirty = true;
-        continue;
-      }
-
-      experiments.set(entry.id, entry);
-    }
-
-    this._experiments = experiments;
-  },
-
-  /*
-   * Update the experiment entries from the experiments
-   * array in the manifest
-   */
-  _updateExperiments(manifestObject) {
-    this._log.trace("_updateExperiments() - experiments: " + JSON.stringify(manifestObject));
-
-    if (manifestObject.version !== MANIFEST_VERSION) {
-      this._log.warning("updateExperiments() - unsupported version " + manifestObject.version);
-    }
-
-    let experiments = new Map(); // The new experiments map
-
-    // Collect new and updated experiments.
-    for (let data of manifestObject.experiments) {
-      let entry = this._experiments.get(data.id);
-
-      if (entry) {
-        if (!entry.updateFromManifestData(data)) {
-          this._log.error("updateExperiments() - Invalid manifest data for " + data.id);
-          continue;
-        }
-      } else {
-        entry = new Experiments.ExperimentEntry(this._policy);
-        if (!entry.initFromManifestData(data)) {
-          continue;
-        }
-      }
-
-      if (entry.shouldDiscard()) {
-        continue;
-      }
-
-      experiments.set(entry.id, entry);
-    }
-
-    // Make sure we keep experiments that are or were running.
-    // We remove them after KEEP_HISTORY_N_DAYS.
-    for (let [id, entry] of this._experiments) {
-      if (experiments.has(id)) {
-        continue;
-      }
-
-      if (!entry.startDate || entry.shouldDiscard()) {
-        this._log.trace("updateExperiments() - discarding entry for " + id);
-        continue;
-      }
-
-      experiments.set(id, entry);
-    }
-
-    this._experiments = experiments;
-    this._dirty = true;
-  },
-
-  getActiveExperimentID() {
-    if (!this._experiments) {
-      return null;
-    }
-    let e = this._getActiveExperiment();
-    if (!e) {
-      return null;
-    }
-    return e.id;
-  },
-
-  getActiveExperimentBranch() {
-    if (!this._experiments) {
-      return null;
-    }
-    let e = this._getActiveExperiment();
-    if (!e) {
-      return null;
-    }
-    return e.branch;
-  },
-
-  _getActiveExperiment() {
-    let enabled = [...this._experiments.values()].filter(experiment => experiment._enabled);
-
-    if (enabled.length == 1) {
-      return enabled[0];
-    }
-
-    if (enabled.length > 1) {
-      this._log.error("getActiveExperimentId() - should not have more than 1 active experiment");
-      throw new Error("have more than 1 active experiment");
-    }
-
-    return null;
-  },
-
-  /**
-   * Disables all active experiments.
-   *
-   * @return Promise<> Promise that will get resolved once the task is done or failed.
-   */
-  disableExperiment(reason) {
-    if (!reason) {
-      throw new Error("Must specify a termination reason.");
-    }
-
-    this._log.trace("disableExperiment()");
-    this._terminateReason = reason;
-    return this._run();
-  },
-
-  /**
-   * The Set of add-on IDs that we know about from manifests.
-   */
-  get _trackedAddonIds() {
-    if (!this._experiments) {
-      return new Set();
-    }
-
-    return new Set([...this._experiments.values()].map(e => e._addonId));
-  },
-
-  /*
-   * Task function to check applicability of experiments, disable the active
-   * experiment if needed and activate the first applicable candidate.
-   */
-  async _evaluateExperiments() {
-    this._log.trace("_evaluateExperiments");
-
-    this._checkForShutdown();
-
-    // The first thing we do is reconcile our state against what's in the
-    // Addon Manager. It's possible that the Addon Manager knows of experiment
-    // add-ons that we don't. This could happen if an experiment gets installed
-    // when we're not listening or if there is a bug in our synchronization
-    // code.
-    //
-    // We have a few options of what to do with unknown experiment add-ons
-    // coming from the Addon Manager. Ideally, we'd convert these to
-    // ExperimentEntry instances and stuff them inside this._experiments.
-    // However, since ExperimentEntry contain lots of metadata from the
-    // manifest and trying to make up data could be error prone, it's safer
-    // to not try. Furthermore, if an experiment really did come from us, we
-    // should have some record of it. In the end, we decide to discard all
-    // knowledge for these unknown experiment add-ons.
-    let installedExperiments = await installedExperimentAddons();
-    let expectedAddonIds = this._trackedAddonIds;
-    let unknownAddons = installedExperiments.filter(a => !expectedAddonIds.has(a.id));
-    if (unknownAddons.length) {
-      this._log.warn("_evaluateExperiments() - unknown add-ons in AddonManager: " +
-                     unknownAddons.map(a => a.id).join(", "));
-
-      await uninstallAddons(unknownAddons);
-    }
-
-    let activeExperiment = this._getActiveExperiment();
-    let activeChanged = false;
-
-    if (!activeExperiment) {
-      // Avoid this pref staying out of sync if there were e.g. crashes.
-      gPrefs.setBoolPref(PREF_ACTIVE_EXPERIMENT, false);
-    }
-
-    // Ensure the active experiment is in the proper state. This may install,
-    // uninstall, upgrade, or enable the experiment add-on. What exactly is
-    // abstracted away from us by design.
-    if (activeExperiment) {
-      let changes;
-      let shouldStopResult = await activeExperiment.shouldStop();
-      if (shouldStopResult.shouldStop) {
-        let expireReasons = ["endTime", "maxActiveSeconds"];
-        let kind, reason;
-
-        if (expireReasons.includes(shouldStopResult.reason[0])) {
-          kind = TELEMETRY_LOG.TERMINATION.EXPIRED;
-          reason = null;
-        } else {
-          kind = TELEMETRY_LOG.TERMINATION.RECHECK;
-          reason = shouldStopResult.reason;
-        }
-        changes = await activeExperiment.stop(kind, reason);
-      } else if (this._terminateReason) {
-        changes = await activeExperiment.stop(this._terminateReason);
-      } else {
-        changes = await activeExperiment.reconcileAddonState();
-      }
-
-      if (changes) {
-        this._dirty = true;
-        activeChanged = true;
-      }
-
-      if (!activeExperiment._enabled) {
-        activeExperiment = null;
-        activeChanged = true;
-      }
-    }
-
-    this._terminateReason = null;
-
-    if (!activeExperiment && gExperimentsEnabled) {
-      for (let [id, experiment] of this._experiments) {
-        let applicable;
-        let reason = null;
-        try {
-          applicable = await experiment.isApplicable();
-        } catch (e) {
-          applicable = false;
-          reason = e;
-        }
-
-        if (!applicable && reason && reason[0] != "was-active") {
-          // Report this from here to avoid over-reporting.
-          let data = [TELEMETRY_LOG.ACTIVATION.REJECTED, id];
-          data = data.concat(reason);
-          const key = TELEMETRY_LOG.ACTIVATION_KEY;
-          TelemetryLog.log(key, data);
-          this._log.trace("evaluateExperiments() - added " + key + " to TelemetryLog: " + JSON.stringify(data));
-        }
-
-        if (!applicable) {
-          continue;
-        }
-
-        this._log.debug("evaluateExperiments() - activating experiment " + id);
-        try {
-          await experiment.start();
-          activeChanged = true;
-          activeExperiment = experiment;
-          this._dirty = true;
-          break;
-        } catch (e) {
-          // On failure, clean up the best we can and try the next experiment.
-          this._log.error("evaluateExperiments() - Unable to start experiment: " + e.message);
-          experiment._enabled = false;
-          await experiment.reconcileAddonState();
-        }
-      }
-    }
-
-    gPrefs.setBoolPref(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
-
-    if (activeChanged || this._firstEvaluate) {
-      Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC);
-      this._firstEvaluate = false;
-    }
-
-    if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
-      try {
-        gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
-        gCrashReporter.annotateCrashReport("ActiveExperimentBranch", activeExperiment.branch);
-      } catch (e) {
-        // It's ok if crash reporting is disabled.
-      }
-    }
-  },
-
-  /*
-   * Schedule the soonest re-check of experiment applicability that is needed.
-   */
-  _scheduleNextRun() {
-    this._checkForShutdown();
-
-    if (this._timer) {
-      this._timer.clear();
-    }
-
-    if (!gExperimentsEnabled || this._experiments.length == 0) {
-      return;
-    }
-
-    let time = null;
-    let now = this._policy.now().getTime();
-    if (this._dirty) {
-      // If we failed to write the cache, we should try again periodically
-      time = now + 1000 * CACHE_WRITE_RETRY_DELAY_SEC;
-    }
-
-    for (let [, experiment] of this._experiments) {
-      let scheduleTime = experiment.getScheduleTime();
-      if (scheduleTime > now) {
-        if (time !== null) {
-          time = Math.min(time, scheduleTime);
-        } else {
-          time = scheduleTime;
-        }
-      }
-    }
-
-    if (time === null) {
-      // No schedule time found.
-      return;
-    }
-
-    this._log.trace("scheduleExperimentEvaluation() - scheduling for " + time + ", now: " + now);
-    this._policy.oneshotTimer(this.notify, time - now, this, "_timer");
-  },
-};
-
-
-/*
- * Represents a single experiment.
- */
-
-Experiments.ExperimentEntry = function(policy) {
-  this._policy = policy || new Experiments.Policy();
-  let log = Log.repository.getLoggerWithMessagePrefix(
-    "Browser.Experiments.Experiments",
-    "ExperimentEntry #" + gExperimentEntryCounter++ + "::");
-  this._log = Object.create(log);
-  this._log.log = (level, string, params) => {
-    if (gExperiments) {
-      gExperiments._addToForensicsLog("ExperimentEntry", string);
-    }
-    log.log(level, string, params);
-  };
-
-  // Is the experiment supposed to be running.
-  this._enabled = false;
-  // When this experiment was started, if ever.
-  this._startDate = null;
-  // When this experiment was ended, if ever.
-  this._endDate = null;
-  // The condition data from the manifest.
-  this._manifestData = null;
-  // For an active experiment, signifies whether we need to update the xpi.
-  this._needsUpdate = false;
-  // A random sample value for comparison against the manifest conditions.
-  this._randomValue = null;
-  // When this entry was last changed for respecting history retention duration.
-  this._lastChangedDate = null;
-  // Has this experiment failed to activate before?
-  this._failedStart = false;
-  // The experiment branch
-  this._branch = null;
-
-  // We grab these from the addon after download.
-  this._name = null;
-  this._description = null;
-  this._homepageURL = null;
-  this._addonId = null;
-};
-
-Experiments.ExperimentEntry.prototype = {
-  MANIFEST_REQUIRED_FIELDS: new Set([
-    "id",
-    "xpiURL",
-    "xpiHash",
-    "startTime",
-    "endTime",
-    "maxActiveSeconds",
-    "appName",
-    "channel",
-  ]),
-
-  MANIFEST_OPTIONAL_FIELDS: new Set([
-    "maxStartTime",
-    "minVersion",
-    "maxVersion",
-    "version",
-    "minBuildID",
-    "maxBuildID",
-    "buildIDs",
-    "os",
-    "locale",
-    "sample",
-    "disabled",
-    "frozen",
-    "jsfilter",
-  ]),
-
-  SERIALIZE_KEYS: new Set([
-    "_enabled",
-    "_manifestData",
-    "_needsUpdate",
-    "_randomValue",
-    "_failedStart",
-    "_name",
-    "_description",
-    "_homepageURL",
-    "_addonId",
-    "_startDate",
-    "_endDate",
-    "_branch",
-  ]),
-
-  DATE_KEYS: new Set([
-    "_startDate",
-    "_endDate",
-  ]),
-
-  UPGRADE_KEYS: new Map([
-    ["_branch", null],
-  ]),
-
-  ADDON_CHANGE_NONE: 0,
-  ADDON_CHANGE_INSTALL: 1,
-  ADDON_CHANGE_UNINSTALL: 2,
-  ADDON_CHANGE_ENABLE: 4,
-
-  /*
-   * Initialize entry from the manifest.
-   * @param data The experiment data from the manifest.
-   * @return boolean Whether initialization succeeded.
-   */
-  initFromManifestData(data) {
-    if (!this._isManifestDataValid(data)) {
-      return false;
-    }
-
-    this._manifestData = data;
-
-    this._randomValue = this._policy.random();
-    this._lastChangedDate = this._policy.now();
-
-    return true;
-  },
-
-  get enabled() {
-    return this._enabled;
-  },
-
-  get id() {
-    return this._manifestData.id;
-  },
-
-  get branch() {
-    return this._branch;
-  },
-
-  set branch(v) {
-    this._branch = v;
-  },
-
-  get startDate() {
-    return this._startDate;
-  },
-
-  get endDate() {
-    if (!this._startDate) {
-      return null;
-    }
-
-    let endTime = 0;
-
-    if (!this._enabled) {
-      return this._endDate;
-    }
-
-    let maxActiveMs = 1000 * this._manifestData.maxActiveSeconds;
-    endTime = Math.min(1000 * this._manifestData.endTime,
-                       this._startDate.getTime() + maxActiveMs);
-
-    return new Date(endTime);
-  },
-
-  get needsUpdate() {
-    return this._needsUpdate;
-  },
-
-  /*
-   * Initialize entry from the cache.
-   * @param data The entry data from the cache.
-   * @return boolean Whether initialization succeeded.
-   */
-  initFromCacheData(data) {
-    for (let [key, dval] of this.UPGRADE_KEYS) {
-      if (!(key in data)) {
-        data[key] = dval;
-      }
-    }
-
-    for (let key of this.SERIALIZE_KEYS) {
-      if (!(key in data) && !this.DATE_KEYS.has(key)) {
-        this._log.error("initFromCacheData() - missing required key " + key);
-        return false;
-      }
-    }
-
-    if (!this._isManifestDataValid(data._manifestData)) {
-      return false;
-    }
-
-    // Dates are restored separately from epoch ms, everything else is just
-    // copied in.
-
-    this.SERIALIZE_KEYS.forEach(key => {
-      if (!this.DATE_KEYS.has(key)) {
-        this[key] = data[key];
-      }
-    });
-
-    this.DATE_KEYS.forEach(key => {
-      if (key in data) {
-        let date = new Date();
-        date.setTime(data[key]);
-        this[key] = date;
-      }
-    });
-
-    // In order for the experiment's data expiration mechanism to work, use the experiment's
-    // |_endData| as the |_lastChangedDate| (if available).
-    this._lastChangedDate = this._endDate ? this._endDate : this._policy.now();
-
-    return true;
-  },
-
-  /*
-   * Returns a JSON representation of this object.
-   */
-  toJSON() {
-    let obj = {};
-
-    // Dates are serialized separately as epoch ms.
-
-    this.SERIALIZE_KEYS.forEach(key => {
-      if (!this.DATE_KEYS.has(key)) {
-        obj[key] = this[key];
-      }
-    });
-
-    this.DATE_KEYS.forEach(key => {
-      if (this[key]) {
-        obj[key] = this[key].getTime();
-      }
-    });
-
-    return obj;
-  },
-
-  /*
-   * Update from the experiment data from the manifest.
-   * @param data The experiment data from the manifest.
-   * @return boolean Whether updating succeeded.
-   */
-  updateFromManifestData(data) {
-    let old = this._manifestData;
-
-    if (!this._isManifestDataValid(data)) {
-      return false;
-    }
-
-    if (this._enabled) {
-      if (old.xpiHash !== data.xpiHash) {
-        // A changed hash means we need to update active experiments.
-        this._needsUpdate = true;
-      }
-    } else if (this._failedStart &&
-               (old.xpiHash !== data.xpiHash) ||
-               (old.xpiURL !== data.xpiURL)) {
-      // Retry installation of previously invalid experiments
-      // if hash or url changed.
-      this._failedStart = false;
-    }
-
-    this._manifestData = data;
-    this._lastChangedDate = this._policy.now();
-
-    return true;
-  },
-
-  /*
-   * Is this experiment applicable?
-   * @return Promise<> Resolved if the experiment is applicable.
-   *                   If it is not applicable it is rejected with
-   *                   a Promise<string> which contains the reason.
-   */
-  isApplicable() {
-    let locale = this._policy.locale();
-    let channel = this._policy.updatechannel();
-    let data = this._manifestData;
-
-    let now = this._policy.now() / 1000; // The manifest times are in seconds.
-    let maxActive = data.maxActiveSeconds || 0;
-    let startSec = (this.startDate || 0) / 1000;
-
-    this._log.trace("isApplicable() - now=" + now
-                    + ", randomValue=" + this._randomValue);
-
-    // Not applicable if it already ran.
-
-    if (!this.enabled && this._endDate) {
-      return Promise.reject(["was-active"]);
-    }
-
-    // Define and run the condition checks.
-
-    let simpleChecks = [
-      { name: "failedStart",
-        condition: () => !this._failedStart },
-      { name: "disabled",
-        condition: () => !data.disabled },
-      { name: "frozen",
-        condition: () => !data.frozen || this._enabled },
-      { name: "startTime",
-        condition: () => now >= data.startTime },
-      { name: "endTime",
-        condition: () => now < data.endTime },
-      { name: "maxStartTime",
-        condition: () => this._startDate || !data.maxStartTime || now <= data.maxStartTime },
-      { name: "maxActiveSeconds",
-        condition: () => !this._startDate || now <= (startSec + maxActive) },
-      { name: "appName",
-        condition: () => !data.appName || data.appName.includes(Services.appinfo.name) },
-      { name: "minBuildID",
-        condition: () => !data.minBuildID || Services.appinfo.platformBuildID >= data.minBuildID },
-      { name: "maxBuildID",
-        condition: () => !data.maxBuildID || Services.appinfo.platformBuildID <= data.maxBuildID },
-      { name: "buildIDs",
-        condition: () => !data.buildIDs || data.buildIDs.includes(Services.appinfo.platformBuildID) },
-      { name: "os",
-        condition: () => !data.os || data.os.includes(Services.appinfo.OS) },
-      { name: "channel",
-        condition: () => !data.channel || data.channel.includes(channel) },
-      { name: "locale",
-        condition: () => !data.locale || data.locale.includes(locale) },
-      { name: "sample",
-        condition: () => data.sample === undefined || this._randomValue <= data.sample },
-      { name: "version",
-        condition: () => !data.version || data.version.includes(Services.appinfo.version) },
-      { name: "minVersion",
-        condition: () => !data.minVersion || Services.vc.compare(Services.appinfo.version, data.minVersion) >= 0 },
-      { name: "maxVersion",
-        condition: () => !data.maxVersion || Services.vc.compare(Services.appinfo.version, data.maxVersion) <= 0 },
-    ];
-
-    for (let check of simpleChecks) {
-      let result = check.condition();
-      if (!result) {
-        this._log.debug("isApplicable() - id="
-                        + data.id + " - test '" + check.name + "' failed");
-        return Promise.reject([check.name]);
-      }
-    }
-
-    if (data.jsfilter) {
-      return this._runFilterFunction(data.jsfilter);
-    }
-
-    return Promise.resolve(true);
-  },
-
-  /*
-   * Run the jsfilter function from the manifest in a sandbox and return the
-   * result (forced to boolean).
-   */
-  async _runFilterFunction(jsfilter) {
-    this._log.trace("runFilterFunction() - filter: " + jsfilter);
-
-    let ssm = Services.scriptSecurityManager;
-    const nullPrincipal = ssm.createNullPrincipal({});
-    let options = {
-      sandboxName: "telemetry experiments jsfilter sandbox",
-      wantComponents: false,
-    };
-
-    let sandbox = Cu.Sandbox(nullPrincipal, options);
-    try {
-      Cu.evalInSandbox(jsfilter, sandbox);
-    } catch (e) {
-      this._log.error("runFilterFunction() - failed to eval jsfilter: " + e.message);
-      throw ["jsfilter-evalfailed"];
-    }
-
-    let currentEnvironment = await TelemetryEnvironment.onInitialized();
-
-    Object.defineProperty(sandbox, "_e",
-      { get: () => Cu.cloneInto(currentEnvironment, sandbox) });
-
-    let result = false;
-    try {
-      result = !!Cu.evalInSandbox("filter({get telemetryEnvironment() { return _e; } })", sandbox);
-    } catch (e) {
-      this._log.debug("runFilterFunction() - filter function failed: "
-                      + e.message + ", " + e.stack);
-      throw ["jsfilter-threw", e.message];
-    } finally {
-      Cu.nukeSandbox(sandbox);
-    }
-
-    if (!result) {
-      throw ["jsfilter-false"];
-    }
-
-    return true;
-  },
-
-  /*
-   * Start running the experiment.
-   *
-   * @return Promise<> Resolved when the operation is complete.
-   */
-  start() {
-    this._log.trace("start() for " + this.id);
-
-    this._enabled = true;
-    return this.reconcileAddonState();
-  },
-
-  // Async install of the addon for this experiment, part of the start task above.
-  async _installAddon() {
-    let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash;
-
-    let install = await addonInstallForURL(this._manifestData.xpiURL, hash);
-    gActiveInstallURLs.add(install.sourceURI.spec);
-
-    return new Promise((resolve, reject) => {
-      let failureHandler = (failureInstall, handler) => {
-        let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
-                     (failureInstall.state || "?") + ", error=" + failureInstall.error;
-        this._log.error("_installAddon() - " + message);
-        this._failedStart = true;
-        gActiveInstallURLs.delete(failureInstall.sourceURI.spec);
-
-        TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
-                        [TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
-
-        reject(new Error(message));
-      };
-
-      let listener = {
-        _expectedID: null,
-
-        onDownloadEnded: downloadEndedInstall => {
-          this._log.trace("_installAddon() - onDownloadEnded for " + this.id);
-
-          if (downloadEndedInstall.existingAddon) {
-            this._log.warn("_installAddon() - onDownloadEnded, addon already installed");
-          }
-
-          if (downloadEndedInstall.addon.type !== "experiment") {
-            this._log.error("_installAddon() - onDownloadEnded, wrong addon type");
-            downloadEndedInstall.cancel();
-          }
-        },
-
-        onInstallStarted: installStartedInstall => {
-          this._log.trace("_installAddon() - onInstallStarted for " + this.id);
-
-          if (installStartedInstall.existingAddon) {
-            this._log.warn("_installAddon() - onInstallStarted, addon already installed");
-          }
-
-          if (installStartedInstall.addon.type !== "experiment") {
-            this._log.error("_installAddon() - onInstallStarted, wrong addon type");
-            return false;
-          }
-          return undefined;
-        },
-
-        onInstallEnded: installEndedInstall => {
-          this._log.trace("_installAddon() - install ended for " + this.id);
-          gActiveInstallURLs.delete(installEndedInstall.sourceURI.spec);
-
-          this._lastChangedDate = this._policy.now();
-          this._startDate = this._policy.now();
-          this._enabled = true;
-
-          TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
-                         [TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]);
-
-          let addon = installEndedInstall.addon;
-          this._name = addon.name;
-          this._addonId = addon.id;
-          this._description = addon.description || "";
-          this._homepageURL = addon.homepageURL || "";
-
-          // Experiment add-ons default to userDisabled=true. Enable if needed.
-          if (addon.userDisabled) {
-            this._log.trace("Add-on is disabled. Enabling.");
-            listener._expectedID = addon.id;
-            AddonManager.addAddonListener(listener);
-            addon.userDisabled = false;
-          } else {
-            this._log.trace("Add-on is enabled. start() completed.");
-            resolve();
-          }
-        },
-
-        onEnabled: addon => {
-          this._log.info("onEnabled() for " + addon.id);
-
-          if (addon.id != listener._expectedID) {
-            return;
-          }
-
-          AddonManager.removeAddonListener(listener);
-          resolve();
-        },
-      };
-
-      ["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"]
-        .forEach(what => {
-          listener[what] = eventInstall => failureHandler(eventInstall, what);
-        });
-
-      install.addListener(listener);
-      install.install();
-    });
-  },
-
-  /**
-   * Stop running the experiment if it is active.
-   *
-   * @param terminationKind (optional)
-   *        The termination kind, e.g. ADDON_UNINSTALLED or EXPIRED.
-   * @param terminationReason (optional)
-   *        The termination reason details for termination kind RECHECK.
-   * @return Promise<> Resolved when the operation is complete.
-   */
-  async stop(terminationKind, terminationReason) {
-    this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind);
-    if (!this._enabled) {
-      throw new Error("Must not call stop() on an inactive experiment.");
-    }
-
-    this._enabled = false;
-    let now = this._policy.now();
-    this._lastChangedDate = now;
-    this._endDate = now;
-
-    let changes = await this.reconcileAddonState();
-    this._logTermination(terminationKind, terminationReason);
-
-    if (terminationKind == TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED) {
-      changes |= this.ADDON_CHANGE_UNINSTALL;
-    }
-
-    return changes;
-  },
-
-  /**
-   * Reconcile the state of the add-on against what it's supposed to be.
-   *
-   * If we are active, ensure the add-on is enabled and up to date.
-   *
-   * If we are inactive, ensure the add-on is not installed.
-   */
-  async reconcileAddonState() {
-    this._log.trace("reconcileAddonState()");
-
-    if (!this._enabled) {
-      if (!this._addonId) {
-        this._log.trace("reconcileAddonState() - Experiment is not enabled and " +
-                        "has no add-on. Doing nothing.");
-        return this.ADDON_CHANGE_NONE;
-      }
-
-      let addon = await this._getAddon();
-      if (!addon) {
-        this._log.trace("reconcileAddonState() - Inactive experiment has no " +
-                        "add-on. Doing nothing.");
-        return this.ADDON_CHANGE_NONE;
-      }
-
-      this._log.info("reconcileAddonState() - Uninstalling add-on for inactive " +
-                     "experiment: " + addon.id);
-      gActiveUninstallAddonIDs.add(addon.id);
-      await uninstallAddons([addon]);
-      gActiveUninstallAddonIDs.delete(addon.id);
-      return this.ADDON_CHANGE_UNINSTALL;
-    }
-
-    // If we get here, we're supposed to be active.
-
-    let changes = 0;
-
-    // That requires an add-on.
-    let currentAddon = await this._getAddon();
-
-    // If we have an add-on but it isn't up to date, uninstall it
-    // (to prepare for reinstall).
-    if (currentAddon && this._needsUpdate) {
-      this._log.info("reconcileAddonState() - Uninstalling add-on because update " +
-                     "needed: " + currentAddon.id);
-      gActiveUninstallAddonIDs.add(currentAddon.id);
-      await uninstallAddons([currentAddon]);
-      gActiveUninstallAddonIDs.delete(currentAddon.id);
-      changes |= this.ADDON_CHANGE_UNINSTALL;
-    }
-
-    if (!currentAddon || this._needsUpdate) {
-      this._log.info("reconcileAddonState() - Installing add-on.");
-      await this._installAddon();
-      changes |= this.ADDON_CHANGE_INSTALL;
-    }
-
-    let addon = await this._getAddon();
-    if (!addon) {
-      throw new Error("Could not obtain add-on for experiment that should be " +
-                      "enabled.");
-    }
-
-    // If we have the add-on and it is enabled, we are done.
-    if (!addon.userDisabled) {
-      return changes;
-    }
-
-    // Check permissions to see if we can enable the addon.
-    if (!(addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
-      throw new Error("Don't have permission to enable addon " + addon.id + ", perm=" + addon.permission);
-    }
-
-    // Experiment addons should not require a restart.
-    if (addon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) {
-      throw new Error("Experiment addon requires a restart: " + addon.id);
-    }
-
-    await new Promise((resolve, reject) => {
-
-      // Else we need to enable it.
-      let listener = {
-        onEnabled: enabledAddon => {
-          if (enabledAddon.id != addon.id) {
-            return;
-          }
-
-          AddonManager.removeAddonListener(listener);
-          resolve();
-        },
-      };
-
-      for (let handler of ["onDisabled", "onOperationCancelled", "onUninstalled"]) {
-        listener[handler] = (evtAddon) => {
-          if (evtAddon.id != addon.id) {
-            return;
-          }
-
-          AddonManager.removeAddonListener(listener);
-          reject("Failed to enable addon " + addon.id + " due to: " + handler);
-        };
-      }
-
-      this._log.info("reconcileAddonState() - Activating add-on: " + addon.id);
-      AddonManager.addAddonListener(listener);
-      addon.userDisabled = false;
-    });
-    changes |= this.ADDON_CHANGE_ENABLE;
-
-    this._log.info("reconcileAddonState() - Add-on has been enabled: " + addon.id);
-    return changes;
-   },
-
-  /**
-   * Obtain the underlying Addon from the Addon Manager.
-   *
-   * @return Promise<Addon|null>
-   */
-  _getAddon() {
-    if (!this._addonId) {
-      return Promise.resolve(null);
-    }
-
-    return AddonManager.getAddonByID(this._addonId).then(addon => {
-      if (addon && addon.appDisabled) {
-        // Don't return PreviousExperiments.
-        return null;
-      }
-
-      return addon;
-    });
-  },
-
-  _logTermination(terminationKind, terminationReason) {
-    if (terminationKind === undefined) {
-      return;
-    }
-
-    if (!(terminationKind in TELEMETRY_LOG.TERMINATION)) {
-      this._log.warn("stop() - unknown terminationKind " + terminationKind);
-      return;
-    }
-
-    let data = [terminationKind, this.id];
-    if (terminationReason) {
-      data = data.concat(terminationReason);
-    }
-
-    TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data);
-  },
-
-  /**
-   * Determine whether an active experiment should be stopped.
-   */
-  shouldStop() {
-    if (!this._enabled) {
-      throw new Error("shouldStop must not be called on disabled experiments.");
-    }
-
-    return new Promise(resolve => {
-      this.isApplicable().then(
-        () => resolve({shouldStop: false}),
-        reason => resolve({shouldStop: true, reason})
-      );
-
-    });
-  },
-
-  /*
-   * Should this be discarded from the cache due to age?
-   */
-  shouldDiscard() {
-    let limit = this._policy.now();
-    limit.setDate(limit.getDate() - KEEP_HISTORY_N_DAYS);
-    return (this._lastChangedDate < limit);
-  },
-
-  /*
-   * Get next date (in epoch-ms) to schedule a re-evaluation for this.
-   * Returns 0 if it doesn't need one.
-   */
-  getScheduleTime() {
-    if (this._enabled) {
-      let startTime = this._startDate.getTime();
-      let maxActiveTime = startTime + 1000 * this._manifestData.maxActiveSeconds;
-      return Math.min(1000 * this._manifestData.endTime, maxActiveTime);
-    }
-
-    if (this._endDate) {
-      return this._endDate.getTime();
-    }
-
-    return 1000 * this._manifestData.startTime;
-  },
-
-  /*
-   * Perform sanity checks on the experiment data.
-   */
-  _isManifestDataValid(data) {
-    this._log.trace("isManifestDataValid() - data: " + JSON.stringify(data));
-
-    for (let key of this.MANIFEST_REQUIRED_FIELDS) {
-      if (!(key in data)) {
-        this._log.error("isManifestDataValid() - missing required key: " + key);
-        return false;
-      }
-    }
-
-    for (let key in data) {
-      if (!this.MANIFEST_OPTIONAL_FIELDS.has(key) &&
-          !this.MANIFEST_REQUIRED_FIELDS.has(key)) {
-        this._log.error("isManifestDataValid() - unknown key: " + key);
-        return false;
-      }
-    }
-
-    return true;
-  },
-};
-
-/**
- * Strip a Date down to its UTC midnight.
- *
- * This will return a cloned Date object. The original is unchanged.
- */
-var stripDateToMidnight = function(d) {
-  let m = new Date(d);
-  m.setUTCHours(0, 0, 0, 0);
-
-  return m;
-};
-
-/**
- * An Add-ons Manager provider that knows about old experiments.
- *
- * This provider exposes read-only add-ons corresponding to previously-active
- * experiments. The existence of this provider (and the add-ons it knows about)
- * facilitates the display of old experiments in the Add-ons Manager UI with
- * very little custom code in that component.
- */
-this.Experiments.PreviousExperimentProvider = function(experiments) {
-  this._experiments = experiments;
-  this._experimentList = [];
-  this._log = Log.repository.getLoggerWithMessagePrefix(
-    "Browser.Experiments.Experiments",
-    "PreviousExperimentProvider #" + gPreviousProviderCounter++ + "::");
-};
-
-this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({
-  name: "PreviousExperimentProvider",
-
-  startup() {
-    this._log.trace("startup()");
-    Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC);
-  },
-
-  shutdown() {
-    this._log.trace("shutdown()");
-    try {
-      Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC);
-    } catch (e) {
-      // Prevent crash in mochitest-browser3 on Mulet
-    }
-  },
-
-  observe(subject, topic, data) {
-    switch (topic) {
-      case EXPERIMENTS_CHANGED_TOPIC:
-        this._updateExperimentList();
-        break;
-    }
-  },
-
-  getAddonByID(id, cb) {
-    for (let experiment of this._experimentList) {
-      if (experiment.id == id) {
-        cb(new PreviousExperimentAddon(experiment));
-        return;
-      }
-    }
-
-    cb(null);
-  },
-
-  getAddonsByTypes(types, cb) {
-    if (types && types.length > 0 && !types.includes("experiment")) {
-      cb([]);
-      return;
-    }
-
-    cb(this._experimentList.map(e => new PreviousExperimentAddon(e)));
-  },
-
-  _updateExperimentList() {
-    return this._experiments.getExperiments().then((experiments) => {
-      let list = experiments.filter(e => !e.active);
-
-      let newMap = new Map(list.map(e => [e.id, e]));
-      let oldMap = new Map(this._experimentList.map(e => [e.id, e]));
-
-      let added = [...newMap.keys()].filter(id => !oldMap.has(id));
-      let removed = [...oldMap.keys()].filter(id => !newMap.has(id));
-
-      for (let id of added) {
-        this._log.trace("updateExperimentList() - adding " + id);
-        let wrapper = new PreviousExperimentAddon(newMap.get(id));
-        AddonManagerPrivate.callInstallListeners("onExternalInstall", null, wrapper, null, false);
-        AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
-      }
-
-      for (let id of removed) {
-        this._log.trace("updateExperimentList() - removing " + id);
-        let wrapper = new PreviousExperimentAddon(oldMap.get(id));
-        AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
-      }
-
-      this._experimentList = list;
-
-      for (let id of added) {
-        let wrapper = new PreviousExperimentAddon(newMap.get(id));
-        AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
-      }
-
-      for (let id of removed) {
-        let wrapper = new PreviousExperimentAddon(oldMap.get(id));
-        AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
-      }
-
-      return this._experimentList;
-    });
-  },
-});
-
-/**
- * An add-on that represents a previously-installed experiment.
- */
-function PreviousExperimentAddon(experiment) {
-  this._id = experiment.id;
-  this._name = experiment.name;
-  this._endDate = experiment.endDate;
-  this._description = experiment.description;
-}
-
-PreviousExperimentAddon.prototype = Object.freeze({
-  // BEGIN REQUIRED ADDON PROPERTIES
-
-  get appDisabled() {
-    return true;
-  },
-
-  get blocklistState() {
-    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-  },
-
-  get creator() {
-    return new AddonManagerPrivate.AddonAuthor("");
-  },
-
-  get foreignInstall() {
-    return false;
-  },
-
-  get id() {
-    return this._id;
-  },
-
-  get isActive() {
-    return false;
-  },
-
-  get isCompatible() {
-    return true;
-  },
-
-  get isPlatformCompatible() {
-    return true;
-  },
-
-  get name() {
-    return this._name;
-  },
-
-  get pendingOperations() {
-    return AddonManager.PENDING_NONE;
-  },
-
-  get permissions() {
-    return 0;
-  },
-
-  get providesUpdatesSecurely() {
-    return true;
-  },
-
-  get scope() {
-    return AddonManager.SCOPE_PROFILE;
-  },
-
-  get type() {
-    return "experiment";
-  },
-
-  get userDisabled() {
-    return true;
-  },
-
-  get version() {
-    return null;
-  },
-
-  // END REQUIRED PROPERTIES
-
-  // BEGIN OPTIONAL PROPERTIES
-
-  get description() {
-    return this._description;
-  },
-
-  get updateDate() {
-    return new Date(this._endDate);
-  },
-
-  // END OPTIONAL PROPERTIES
-
-  // BEGIN REQUIRED METHODS
-
-  isCompatibleWith(appVersion, platformVersion) {
-    return true;
-  },
-
-  findUpdates(listener, reason, appVersion, platformVersion) {
-    AddonManagerPrivate.callNoUpdateListeners(this, listener, reason,
-                                              appVersion, platformVersion);
-  },
-
-  // END REQUIRED METHODS
-
-  /**
-   * The end-date of the experiment, required for the Addon Manager UI.
-   */
-
-   get endDate() {
-     return this._endDate;
-   },
-
-});
deleted file mode 100644
--- a/browser/experiments/Experiments.manifest
+++ /dev/null
@@ -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
-
-
deleted file mode 100644
--- a/browser/experiments/ExperimentsService.js
+++ /dev/null
@@ -1,105 +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";
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-ChromeUtils.defineModuleGetter(this, "Experiments",
-                               "resource:///modules/experiments/Experiments.jsm");
-ChromeUtils.defineModuleGetter(this, "OS",
-                               "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "CommonUtils",
-                               "resource://services-common/utils.js");
-ChromeUtils.defineModuleGetter(this, "TelemetryUtils",
-                               "resource://gre/modules/TelemetryUtils.jsm");
-
-
-const PREF_EXPERIMENTS_ENABLED  = "experiments.enabled";
-const PREF_ACTIVE_EXPERIMENT    = "experiments.activeExperiment"; // whether we have an active experiment
-const DELAY_INIT_MS             = 30 * 1000;
-
-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]),
-
-  get _experimentsEnabled() {
-    // We can enable experiments if either unified Telemetry or FHR is on, and the user
-    // has opted into Telemetry.
-    return Services.prefs.getBoolPref(PREF_EXPERIMENTS_ENABLED, false) &&
-           TelemetryUtils.isTelemetryEnabled;
-  },
-
-  notify(timer) {
-    if (!this._experimentsEnabled) {
-      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().catch(error => {
-        // Don't throw, as this breaks tests. In any case the best we can do here
-        // is to log the failure.
-        Cu.reportError(error);
-      });
-    }
-  },
-
-  _delayedInit() {
-    if (!this._initialized) {
-      this._initialized = true;
-      Experiments.instance(); // for side effects
-    }
-  },
-
-  observe(subject, topic, data) {
-    switch (topic) {
-      case "profile-after-change":
-        if (this._experimentsEnabled) {
-          Services.obs.addObserver(this, "quit-application");
-          Services.obs.addObserver(this, "sessionstore-state-finalized");
-          Services.obs.addObserver(this, "EM-loaded");
-
-          if (Services.prefs.getBoolPref(PREF_ACTIVE_EXPERIMENT, false)) {
-            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]);
deleted file mode 100644
--- a/browser/experiments/Makefile.in
+++ /dev/null
@@ -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
deleted file mode 100644
--- a/browser/experiments/docs/index.rst
+++ /dev/null
@@ -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
deleted file mode 100644
--- a/browser/experiments/docs/manifest.rst
+++ /dev/null
@@ -1,431 +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`` or ``Fennec``.
-
-   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
-   ``LocaleService.getAppLocaleAsLangTag()``.
-   For infamous `ja-JP-mac` case, this will return it in
-   the language tag form (`ja-JP-mac`).
-
-   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 inadvertently 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; }"
-       }
-     ]
-   }
deleted file mode 100644
--- a/browser/experiments/moz.build
+++ /dev/null
@@ -1,24 +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/.
-
-with Files("**"):
-    BUG_COMPONENT = ("Toolkit", "Telemetry")
-
-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'
-
-with Files('docs/**'):
-    SCHEDULES.exclusive = ['docs']
deleted file mode 100644
--- a/browser/experiments/test/addons/experiment-1/install.rdf
+++ /dev/null
@@ -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>
deleted file mode 100644
--- a/browser/experiments/test/addons/experiment-1a/install.rdf
+++ /dev/null
@@ -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>
deleted file mode 100644
--- a/browser/experiments/test/addons/experiment-2/install.rdf
+++ /dev/null
@@ -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>
deleted file mode 100644
--- a/browser/experiments/test/addons/experiment-racybranch/bootstrap.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* exported startup, shutdown, install, uninstall */
-
-ChromeUtils.import("resource:///modules/experiments/Experiments.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-var gStarted = false;
-
-function startup(data, reasonCode) {
-  if (gStarted) {
-    return;
-  }
-  gStarted = true;
-
-  // delay realstartup to trigger the race condition
-  Services.tm.dispatchToMainThread(realstartup);
-}
-
-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)
-    .catch(Cu.reportError);
-}
-
-function shutdown() { }
-function install() { }
-function uninstall() { }
deleted file mode 100644
--- a/browser/experiments/test/addons/experiment-racybranch/install.rdf
+++ /dev/null
@@ -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>
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/.eslintrc.js
+++ /dev/null
@@ -1,14 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "plugin:mozilla/xpcshell-test"
-  ],
-
-  "rules": {
-    "no-unused-vars": ["error", {
-      "vars": "all",
-      "args": "none"
-    }]
-  }
-};
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/experiments_1.manifest
+++ /dev/null
@@ -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; }"
-    }
-  ]
-}
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/head.js
+++ /dev/null
@@ -1,193 +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 */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/osfile.jsm");
-ChromeUtils.import("resource://testing-common/AddonManagerTesting.jsm");
-ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
-
-ChromeUtils.defineModuleGetter(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";
-
-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.nsIFile);
-  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);
-  Services.obs.notifyObservers(null, "sessionstore-windows-restored");
-}
-
-function getExperimentAddons(previous = false) {
-  return new Promise(resolve => {
-
-    AddonManager.getAddonsByTypes(["experiment"], (addons) => {
-      if (previous) {
-        resolve(addons);
-      } else {
-        resolve(addons.filter(a => !a.appDisabled));
-      }
-    });
-
-  });
-}
-
-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);
-    },
-  });
-}
-
-Services.telemetry.canRecordExtended = true;
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_activate.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.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;
-}
-
-add_task(async 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());
-  registerCleanupFunction(() => 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) {
-  return new Promise(resolve => {
-    experiment.isApplicable().then(
-      result => resolve({ applicable: true,  reason: null }),
-      reason => resolve({ applicable: false, reason })
-    );
-
-  });
-}
-
-add_task(async 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 = await isApplicable(experiment);
-  Assert.equal(result.applicable, false, "Experiment should not be applicable.");
-  Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
-
-  defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
-  result = await isApplicable(experiment);
-  Assert.equal(result.applicable, true, "Experiment should now be applicable.");
-  Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
-
-  let changes = await experiment.start();
-  Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
-  addons = await 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 = await experiment.stop();
-  Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
-  addons = await 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 = await experiment.start();
-  Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
-  addons = await 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 = await experiment.shouldStop();
-  Assert.equal(result.shouldStop, false, "shouldStop should be false.");
-  Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
-  addons = await 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 = await experiment.shouldStop();
-  Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
-  changes = await experiment.stop();
-  Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
-  Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
-  addons = await 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 {
-    await 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 = await experiment.start();
-  Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL);
-  await experiment.stop();
-  gPolicy.ignoreHashes = false;
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_api.js
+++ /dev/null
@@ -1,1642 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.import("resource://testing-common/AddonManagerTesting.jsm");
-
-ChromeUtils.defineModuleGetter(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;
-var gTimerScheduleOffset = -1;
-
-function uninstallExperimentAddons() {
-  return (async function() {
-    let addons = await getExperimentAddons();
-    for (let a of addons) {
-      await AddonManagerTesting.uninstallAddonByID(a.id);
-    }
-  })();
-}
-
-function testCleanup(experimentsInstance) {
-  return (async function() {
-    await promiseRestartManager();
-    await uninstallExperimentAddons();
-    await removeCacheFile();
-  })();
-}
-
-add_task(async 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();
-  });
-  registerCleanupFunction(() => 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) => gTimerScheduleOffset = timeout,
-  });
-});
-
-add_task(async function test_contract() {
-  Cc["@mozilla.org/browser/experiments-service;1"].getService();
-});
-
-// Test basic starting and stopping of experiments.
-
-add_task(async function test_getExperiments() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // 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"],
-      },
-    ],
-  };
-
-  // 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.",
-    },
-  ];
-
-  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;
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  Assert.equal(experiments.getActiveExperimentID(), null,
-               "getActiveExperimentID should return null");
-
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
-
-  try {
-    await experiments.getExperimentBranch();
-    Assert.ok(false, "getExperimentBranch should fail with no experiment");
-  } catch (e) {
-    Assert.ok(true, "getExperimentBranch correctly threw");
-  }
-
-  // Trigger update, clock set for experiment 1 to start.
-
-  now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
-               "getActiveExperimentID should return the active experiment1");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "An experiment add-on was installed.");
-
-  experimentListData[1].active = true;
-  experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
-  for (let k of Object.keys(experimentListData[1])) {
-    Assert.equal(experimentListData[1][k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  let b = await experiments.getExperimentBranch();
-  Assert.strictEqual(b, null, "getExperimentBranch should return null by default");
-
-  b = await experiments.getExperimentBranch(EXPERIMENT1_ID);
-  Assert.strictEqual(b, null, "getExperimentsBranch should return null (with id)");
-
-  await experiments.setExperimentBranch(EXPERIMENT1_ID, "foo");
-  b = await experiments.getExperimentBranch();
-  Assert.strictEqual(b, "foo", "getExperimentsBranch should return the set value");
-
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY,
-               "Experiment re-evaluation should have been scheduled correctly.");
-
-  // Trigger update, clock set for experiment 1 to stop.
-
-  now = futureDate(endDate1, 1000);
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  Assert.equal(experiments.getActiveExperimentID(), null,
-               "getActiveExperimentID should return null again");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
-
-  experimentListData[1].active = false;
-  experimentListData[1].endDate = now.getTime();
-  for (let k of Object.keys(experimentListData[1])) {
-    Assert.equal(experimentListData[1][k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  Assert.equal(gTimerScheduleOffset, startDate2 - now,
-               "Experiment re-evaluation should have been scheduled correctly.");
-
-  // Trigger update, clock set for experiment 2 to start.
-  // Use notify() to provide for coverage of that path.
-
-  now = startDate2;
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-
-  await experiments.notify();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT2_ID,
-               "getActiveExperimentID should return the active experiment2");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "An experiment add-on is installed.");
-
-  experimentListData[0].active = true;
-  experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
-  for (let i = 0; i < experimentListData.length; ++i) {
-    let entry = experimentListData[i];
-    for (let k of Object.keys(entry)) {
-      Assert.equal(entry[k], list[i][k],
-                   "Entry " + i + " - Property '" + k + "' should match reference data.");
-    }
-  }
-
-  Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY,
-               "Experiment re-evaluation should have been scheduled correctly.");
-
-  // Trigger update, clock set for experiment 2 to stop.
-
-  now = futureDate(startDate2, 10 * MS_IN_ONE_DAY + 1000);
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-  await experiments.notify();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  Assert.equal(experiments.getActiveExperimentID(), null,
-               "getActiveExperimentID should return null again2");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "No experiments add-ons are installed.");
-
-  experimentListData[0].active = false;
-  experimentListData[0].endDate = now.getTime();
-  for (let i = 0; i < experimentListData.length; ++i) {
-    let entry = experimentListData[i];
-    for (let k of Object.keys(entry)) {
-      Assert.equal(entry[k], list[i][k],
-                   "Entry " + i + " - Property '" + k + "' should match reference data.");
-    }
-  }
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-add_task(async function test_getActiveExperimentID() {
-  // Check that getActiveExperimentID returns the correct result even
-  // after .uninit()
-
-  // 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);
-
-  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"],
-      },
-    ],
-  };
-
-  let now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
-  gTimerScheduleOffset = -1;
-  defineNow(gPolicy, now);
-
-  let experiments = new Experiments.Experiments(gPolicy);
-  await experiments.updateManifest();
-
-  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
-               "getActiveExperimentID should return the active experiment1");
-
-  await promiseRestartManager();
-  Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
-               "getActiveExperimentID should return the active experiment1 after uninit()");
-
-  await testCleanup(experiments);
-});
-
-// Test that we handle the experiments addon already being
-// installed properly.
-// We should just pave over them.
-
-add_task(async function test_addonAlreadyInstalled() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate  = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate    = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "1 add-on is installed.");
-
-  // Install conflicting addon.
-
-  await AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "1 add-on is installed.");
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-add_task(async function test_lastActiveToday() {
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  replaceExperiments(experiments, FAKE_EXPERIMENTS_1);
-
-  let e = await experiments.getExperiments();
-  Assert.equal(e.length, 1, "Monkeypatch successful.");
-  Assert.equal(e[0].id, "id1", "ID looks sane");
-  Assert.ok(e[0].active, "Experiment is active.");
-
-  let lastActive = await experiments.lastActiveToday();
-  Assert.equal(e[0], lastActive, "Last active object is expected.");
-
-  replaceExperiments(experiments, FAKE_EXPERIMENTS_2);
-  e = await experiments.getExperiments();
-  Assert.equal(e.length, 2, "Monkeypatch successful.");
-
-  defineNow(gPolicy, e[0].endDate);
-
-  lastActive = await experiments.lastActiveToday();
-  Assert.ok(lastActive, "Have a last active experiment");
-  Assert.equal(lastActive, e[0], "Last active object is expected.");
-
-  await testCleanup(experiments);
-});
-
-// Test explicitly disabling experiments.
-
-add_task(async function test_disableExperiment() {
-  // Dates this test is based on.
-
-  let startDate = new Date(2004, 10, 9, 12);
-  let endDate   = futureDate(startDate, 100 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  // Data to compare the result of Experiments.getExperiments() against.
-
-  let experimentInfo = {
-    id: EXPERIMENT1_ID,
-    name: EXPERIMENT1_NAME,
-    description: "Yet another experiment that experiments experimentally.",
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set for the experiment to start.
-
-  let now = futureDate(startDate, 5 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-
-  experimentInfo.active = true;
-  experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  // Test disabling the experiment.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.disableExperiment("foo");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
-
-  experimentInfo.active = false;
-  experimentInfo.endDate = now.getTime();
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  // Test that updating the list doesn't re-enable it.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
-
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  await testCleanup(experiments);
-});
-
-add_task(async function test_disableExperimentsFeature() {
-  // Dates this test is based on.
-
-  let startDate = new Date(2004, 10, 9, 12);
-  let endDate   = futureDate(startDate, 100 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  // Data to compare the result of Experiments.getExperiments() against.
-
-  let experimentInfo = {
-    id: EXPERIMENT1_ID,
-    name: EXPERIMENT1_NAME,
-    description: "Yet another experiment that experiments experimentally.",
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-  Assert.equal(experiments.enabled, true, "Experiments feature should be enabled.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  let now = futureDate(startDate, 5 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-
-  experimentInfo.active = true;
-  experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  // Test disabling experiments.
-
-  experiments._toggleExperimentsEnabled(false);
-  await experiments.notify();
-  Assert.equal(experiments.enabled, false, "Experiments feature should be disabled now.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
-
-  experimentInfo.active = false;
-  experimentInfo.endDate = now.getTime();
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  // Test that updating the list doesn't re-enable it.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  try {
-    await experiments.updateManifest();
-  } catch (e) {
-    // Exception expected, the feature is disabled.
-  }
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
-
-  for (let k of Object.keys(experimentInfo)) {
-    Assert.equal(experimentInfo[k], list[0][k],
-                 "Property " + k + " should match reference data.");
-  }
-
-  await testCleanup(experiments);
-});
-
-// Test that after a failed experiment install:
-// * the next applicable experiment gets installed
-// * changing the experiments data later triggers re-evaluation
-
-add_task(async function test_installFailure() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-      {
-        id:               EXPERIMENT2_ID,
-        xpiURL:           gDataRoot + EXPERIMENT2_XPI_NAME,
-        xpiHash:          EXPERIMENT2_XPI_SHA1,
-        startTime:        dateToSeconds(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  // Data to compare the result of Experiments.getExperiments() against.
-
-  let experimentListData = [
-    {
-      id: EXPERIMENT1_ID,
-      name: EXPERIMENT1_NAME,
-      description: "Yet another experiment that experiments experimentally.",
-    },
-    {
-      id: EXPERIMENT2_ID,
-      name: "Test experiment 2",
-      description: "And yet another experiment that experiments experimentally.",
-    },
-  ];
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for experiment 1 & 2 to start,
-  // invalid hash for experiment 1.
-  // Order in the manifest matters, so we should start experiment 1,
-  // fail to install it & start experiment 2 instead.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].xpiHash = "sha1:0000000000000000000000000000000000000000";
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 2 should be active.");
-
-  // Trigger update, clock set for experiment 2 to stop.
-
-  now = futureDate(now, 20 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  experimentListData[0].active = false;
-  experimentListData[0].endDate = now;
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment should not be active.");
-
-  // Trigger update with a fixed entry for experiment 1,
-  // which should get re-evaluated & started now.
-
-  now = futureDate(now, 20 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].xpiHash = EXPERIMENT1_XPI_SHA1;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  experimentListData[0].active = true;
-  experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
-
-  for (let i = 0; i < experimentListData.length; ++i) {
-    let entry = experimentListData[i];
-    for (let k of Object.keys(entry)) {
-      Assert.equal(entry[k], list[i][k],
-                   "Entry " + i + " - Property '" + k + "' should match reference data.");
-    }
-  }
-
-  await testCleanup(experiments);
-});
-
-// Test that after an experiment was disabled by user action,
-// the experiment is not activated again if manifest data changes.
-
-add_task(async function test_userDisabledAndUpdated() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for experiment 1 to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-  let todayActive = await experiments.lastActiveToday();
-  Assert.ok(todayActive, "Last active for today reports a value.");
-  Assert.equal(todayActive.id, list[0].id, "The entry is what we expect.");
-
-  // Explicitly disable an experiment.
-
-  now = futureDate(now, 20 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.disableExperiment("foo");
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment should not be active anymore.");
-  todayActive = await experiments.lastActiveToday();
-  Assert.ok(todayActive, "Last active for today still returns a value.");
-  Assert.equal(todayActive.id, list[0].id, "The ID is still the same.");
-
-  // Trigger an update with a faked change for experiment 1.
-
-  now = futureDate(now, 20 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  experiments._experiments.get(EXPERIMENT1_ID)._manifestData.xpiHash =
-    "sha1:0000000000000000000000000000000000000000";
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should not have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment should still be inactive.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that changing the hash for an active experiments triggers an
-// update for it.
-
-add_task(async function test_updateActiveExperiment() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  let todayActive = await experiments.lastActiveToday();
-  Assert.equal(todayActive, null, "No experiment active today.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-  Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
-  todayActive = await experiments.lastActiveToday();
-  Assert.ok(todayActive, "todayActive() returns a value.");
-  Assert.equal(todayActive.id, list[0].id, "It returns the active experiment.");
-
-  // Trigger an update for the active experiment by changing it's hash (and xpi)
-  // in the manifest.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].xpiHash = EXPERIMENT1A_XPI_SHA1;
-  gManifestObject.experiments[0].xpiURL = gDataRoot + EXPERIMENT1A_XPI_NAME;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
-  Assert.equal(list[0].name, EXPERIMENT1A_NAME, "Experiments name should have been updated.");
-  todayActive = await experiments.lastActiveToday();
-  Assert.equal(todayActive.id, list[0].id, "last active today is still sane.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Tests that setting the disable flag for an active experiment
-// stops it.
-
-add_task(async function test_disableActiveExperiment() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-
-  // Trigger an update with the experiment being disabled.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].disabled = true;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment 1 should be disabled.");
-
-  // Check that the experiment stays disabled.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  delete gManifestObject.experiments[0].disabled;
-  await experiments.updateManifest();
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment 1 should still be disabled.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that:
-// * setting the frozen flag for a not-yet-started experiment keeps
-//   it from starting
-// * after a removing the frozen flag, the experiment can still start
-
-add_task(async function test_freezePendingExperiment() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start but frozen.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].frozen = true;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should have not been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should have no entries yet.");
-
-  // Trigger an update with the experiment not being frozen anymore.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  delete gManifestObject.experiments[0].frozen;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active now.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that setting the frozen flag for an active experiment doesn't
-// stop it.
-
-add_task(async function test_freezeActiveExperiment() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-  Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
-
-  // Trigger an update with the experiment being disabled.
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].frozen = true;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that removing an active experiment from the manifest doesn't
-// stop it.
-
-add_task(async function test_removeActiveExperiment() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate  = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate    = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
-  let startDate2 = futureDate(baseDate, 20000 * MS_IN_ONE_DAY);
-  let endDate2   = futureDate(baseDate, 30000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-      {
-        id:               EXPERIMENT2_ID,
-        xpiURL:           gDataRoot + EXPERIMENT1_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.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-  Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
-
-  // Trigger an update with experiment 1 missing from the manifest
-
-  now = futureDate(now, 1 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gManifestObject.experiments[0].frozen = true;
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that we correctly handle experiment start & install failures.
-
-add_task(async function test_invalidUrl() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate   = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
-
-  // The manifest data we test with.
-
-  gManifestObject = {
-    "version": 1,
-    experiments: [
-      {
-        id:               EXPERIMENT1_ID,
-        xpiURL:           gDataRoot + EXPERIMENT1_XPI_NAME + ".invalid",
-        xpiHash:          EXPERIMENT1_XPI_SHA1,
-        startTime:        0,
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set for the experiment to start.
-
-  let now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  gTimerScheduleOffset = null;
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled.");
-
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// Test that we handle it properly when active experiment addons are being
-// uninstalled.
-
-add_task(async function test_unexpectedUninstall() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // Dates the following tests are based on.
-
-  let baseDate   = new Date(2014, 5, 1, 12);
-  let startDate  = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
-  let endDate    = futureDate(baseDate, 10000 * 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(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  // Trigger update, clock set to before any activation.
-
-  let now = baseDate;
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Trigger update, clock set for the experiment to start.
-
-  now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, true, "Experiment 1 should be active.");
-
-  // Uninstall the addon through the addon manager instead of stopping it through
-  // the experiments API.
-
-  await AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID);
-  await experiments._mainTask;
-
-  await experiments.notify();
-
-  list = await experiments.getExperiments();
-  Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
-  Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.equal(list[0].active, false, "Experiment 1 should not be active anymore.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await testCleanup(experiments);
-});
-
-// If the Addon Manager knows of an experiment that we don't, it should get
-// uninstalled.
-add_task(async function testUnknownExperimentsUninstalled() {
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present.");
-
-  // Simulate us not listening.
-  experiments._unregisterWithAddonManager();
-  await AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
-  experiments._registerWithAddonManager();
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager");
-
-  // Simulate no known experiments.
-  gManifestObject = {
-    "version": 1,
-    experiments: [],
-  };
-
-  await experiments.updateManifest();
-  let fromManifest = await experiments.getExperiments();
-  Assert.equal(fromManifest.length, 0, "No experiments known in manifest.");
-
-  // And the unknown add-on should be gone.
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Experiment 1 was uninstalled.");
-
-  await testCleanup(experiments);
-});
-
-// If someone else installs an experiment add-on, we detect and stop that.
-add_task(async function testForeignExperimentInstall() {
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  gManifestObject = {
-    "version": 1,
-    experiments: [],
-  };
-
-  await experiments.init();
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
-
-  let failed = false;
-  try {
-    await AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
-  } catch (ex) {
-    failed = true;
-  }
-  Assert.ok(failed, "Add-on install should not have completed successfully");
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
-
-  await testCleanup(experiments);
-});
-
-// Experiment add-ons will be disabled after Addon Manager restarts. Ensure
-// we enable them automatically.
-add_task(async function testEnabledAfterRestart() {
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  gManifestObject = {
-    "version": 1,
-    experiments: [
-      {
-        id: EXPERIMENT1_ID,
-        xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
-        xpiHash: EXPERIMENT1_XPI_SHA1,
-        startTime: gPolicy.now().getTime() / 1000 - 60,
-        endTime: gPolicy.now().getTime() / 1000 + 60,
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName: ["XPCShell"],
-        channel: ["nightly"],
-      },
-    ],
-  };
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
-
-  await experiments.updateManifest();
-  let fromManifest = await experiments.getExperiments();
-  Assert.equal(fromManifest.length, 1, "A single experiment is known.");
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
-  Assert.ok(addons[0].isActive, "That experiment is active.");
-
-  dump("Restarting Addon Manager\n");
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "The experiment is still there after restart.");
-  Assert.ok(addons[0].userDisabled, "But it is disabled.");
-  Assert.equal(addons[0].isActive, false, "And not active.");
-
-  await experiments.updateManifest();
-  Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated.");
-
-  await testCleanup(experiments);
-});
-
-// If experiment add-ons were ever started, maxStartTime shouldn't be evaluated
-// anymore. Ensure that if maxStartTime is passed but experiment has started
-// already, maxStartTime does not cause deactivation.
-
-add_task(async function testMaxStartTimeEvaluation() {
-
-  // Dates the following tests are based on.
-
-  let startDate    = new Date(2014, 5, 1, 12);
-  let now          = futureDate(startDate, 10 * MS_IN_ONE_DAY);
-  let maxStartDate = futureDate(startDate, 100 * MS_IN_ONE_DAY);
-  let endDate      = futureDate(startDate, 1000 * MS_IN_ONE_DAY);
-
-  defineNow(gPolicy, now);
-
-  // The manifest data we test with.
-  // We set a value for maxStartTime.
-
-  gManifestObject = {
-    "version": 1,
-    experiments: [
-      {
-        id:               EXPERIMENT1_ID,
-        xpiURL:           gDataRoot + EXPERIMENT1_XPI_NAME,
-        xpiHash:          EXPERIMENT1_XPI_SHA1,
-        startTime:        dateToSeconds(startDate),
-        endTime:          dateToSeconds(endDate),
-        maxActiveSeconds: 1000 * SEC_IN_ONE_DAY,
-        maxStartTime:     dateToSeconds(maxStartDate),
-        appName:          ["XPCShell"],
-        channel:          ["nightly"],
-      },
-    ],
-  };
-
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
-
-  await experiments.updateManifest();
-  let fromManifest = await experiments.getExperiments();
-  Assert.equal(fromManifest.length, 1, "A single experiment is known.");
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
-  Assert.ok(addons[0].isActive, "That experiment is active.");
-
-  dump("Setting current time to maxStartTime + 100 days and reloading manifest\n");
-  now = futureDate(maxStartDate, 100 * MS_IN_ONE_DAY);
-  defineNow(gPolicy, now);
-  await experiments.updateManifest();
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "The experiment is still there.");
-  Assert.ok(addons[0].isActive, "It is still active.");
-
-  await testCleanup(experiments);
-});
-
-// Test coverage for an add-on uninstall disabling the experiment and that it stays
-// disabled over restarts.
-add_task(async function test_foreignUninstallAndRestart() {
-  let experiments = new Experiments.Experiments(gPolicy);
-
-  gManifestObject = {
-    "version": 1,
-    experiments: [
-      {
-        id: EXPERIMENT1_ID,
-        xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
-        xpiHash: EXPERIMENT1_XPI_SHA1,
-        startTime: gPolicy.now().getTime() / 1000 - 60,
-        endTime: gPolicy.now().getTime() / 1000 + 60,
-        maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
-        appName: ["XPCShell"],
-        channel: ["nightly"],
-      },
-    ],
-  };
-
-  let addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
-
-  await experiments.updateManifest();
-  let experimentList = await experiments.getExperiments();
-  Assert.equal(experimentList.length, 1, "A single experiment is known.");
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
-  Assert.ok(addons[0].isActive, "That experiment is active.");
-
-  await AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID);
-  await experiments._mainTask;
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "Experiment add-on should have been removed.");
-
-  experimentList = await experiments.getExperiments();
-  Assert.equal(experimentList.length, 1, "A single experiment is known.");
-  Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.ok(!experimentList[0].active, "Experiment 1 should not be active anymore.");
-
-  // Fake restart behaviour.
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments.updateManifest();
-
-  addons = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "No experiment add-ons installed.");
-
-  experimentList = await experiments.getExperiments();
-  Assert.equal(experimentList.length, 1, "A single experiment is known.");
-  Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
-  Assert.ok(!experimentList[0].active, "Experiment 1 should not be active.");
-
-  await testCleanup(experiments);
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_cache.js
+++ /dev/null
@@ -1,411 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.defineModuleGetter(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;
-
-add_task(async function test_setup() {
-  loadAddonManager();
-  await 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();
-  });
-  registerCleanupFunction(() => 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(async 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);
-  await experiments.updateManifest();
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-  checkExperimentSerializations(experiments._experiments.values());
-
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-
-  await experiments._run();
-  list = await 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);
-
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments._run();
-
-  list = await 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 = await experiments.getExperimentBranch(EXPERIMENT1_ID);
-  Assert.strictEqual(branch, null);
-
-  await experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch");
-  branch = await 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);
-
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments._run();
-
-  list = await 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 = await 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);
-
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments._run();
-
-  list = await 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);
-
-  await promiseRestartManager();
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments._run();
-
-  list = await 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.
-
-  await experiments._toggleExperimentsEnabled(false);
-  await promiseRestartManager();
-  await removeCacheFile();
-});
-
-add_task(async 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 = async function(newDate) {
-    now = newDate;
-    defineNow(gPolicy, now);
-
-    await promiseRestartManager();
-    experiments = new Experiments.Experiments(gPolicy);
-    await experiments._run();
-  };
-
-  // Trigger update & re-init, clock set to before any activation.
-  now = baseDate;
-  defineNow(gPolicy, now);
-
-  experiments = new Experiments.Experiments(gPolicy);
-  await experiments.updateManifest();
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  // Re-init, clock set for experiment 1 to start...
-  await setDateAndRestartExperiments(startDates[0]);
-  list = await 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.
-  await setDateAndRestartExperiments(endDates[0]);
-
-  // The experiment just ended, it should still be in the cache, but marked
-  // as finished.
-  list = await 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.
-  await setDateAndRestartExperiments(startDates[1]);
-
-  // The experiments cache should contain the finished experiment and the
-  // one that's still running.
-  list = await 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.
-  await 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 = await 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.
-  await 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 = await 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);
-  await experiments.updateManifest();
-  validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]);
-
-  // Cleanup.
-  await experiments._toggleExperimentsEnabled(false);
-  await promiseRestartManager();
-  await removeCacheFile();
-});
-
-add_task(async function test_invalid_cache() {
-  // Save uncompressed data to the cache file to trigger a loading error.
-  let encoder = new TextEncoder();
-  let data = encoder.encode("foo");
-
-  let path = OS.Path.join(OS.Constants.Path.profileDir, "experiments.json");
-  let options = { tmpPath: path + ".tmp" };
-  await OS.File.writeAtomic(path, data, options);
-
-  // Trigger loading from the cache. This should not throw and gracefully recover.
-  let experiments = new Experiments.Experiments(gPolicy);
-  let list = await experiments.getExperiments();
-
-  Assert.deepEqual(list, [], "The experiments cache should be empty.");
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_cacherace.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-/* eslint-disable mozilla/no-arbitrary-setTimeout */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.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;
-
-add_task(async function test_setup() {
-  loadAddonManager();
-  await 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();
-  });
-  registerCleanupFunction(() => 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 = ChromeUtils.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)),
-      },
-    ],
-  };
-
-  info("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;
-
-  await experiments.updateManifest();
-  let active = experiments._getActiveExperiment();
-  Assert.ok(active);
-  Assert.equal(active.branch, "racy-set");
-  Assert.ok(!experiments._dirty);
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_conditions.js
+++ /dev/null
@@ -1,321 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-
-ChromeUtils.import("resource:///modules/experiments/Experiments.jsm");
-ChromeUtils.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();
-}
-
-add_task(async function test_setup() {
-  createAppInfo();
-  do_get_profile();
-  startAddonManagerOnly();
-  await 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(async 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;
-
-    await 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(async 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]);
-
-    await 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(async function test_shutdown() {
-  await TelemetryController.testShutdown();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_disableExperiments.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.import("resource://testing-common/AddonManagerTesting.jsm");
-
-ChromeUtils.defineModuleGetter(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;
-
-add_task(async 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();
-  });
-  registerCleanupFunction(() => 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(async function test_disableExperiments() {
-  const OBSERVER_TOPIC = "experiments-changed";
-  let observerFireCount = 0;
-  let expectedObserverFireCount = 0;
-  let observer = () => ++observerFireCount;
-  Services.obs.addObserver(observer, OBSERVER_TOPIC);
-
-  // 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);
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-  let addons = await 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);
-
-  await experiments.updateManifest();
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await 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 = await 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);
-  await experiments._mainTask;
-
-  Assert.equal(observerFireCount, ++expectedObserverFireCount,
-               "Experiments observer should have been called.");
-
-  list = await 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 = await 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 {
-    await experiments.updateManifest();
-  } catch (e) {
-    // This exception is expected, we rethrow everything else
-    if (e.message != "experiments are disabled") {
-      throw e;
-    }
-  }
-
-  experiments.notify();
-  await experiments._mainTask;
-
-  Assert.equal(observerFireCount, expectedObserverFireCount,
-               "Experiments observer should not have been called.");
-
-  list = await 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 = await getExperimentAddons();
-  Assert.equal(addons.length, 0, "There should still be no experiment add-on installed.");
-
-  // Cleanup.
-
-  Services.obs.removeObserver(observer, OBSERVER_TOPIC);
-  await promiseRestartManager();
-  await removeCacheFile();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_fetch.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/osfile.jsm");
-ChromeUtils.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());
-  registerCleanupFunction(() => 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(async 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.");
-  await ex.updateManifest();
-  Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
-
-  await promiseRestartManager();
-});
-
-add_task(async function test_checkCache() {
-  let ex = new Experiments.Experiments(gPolicy);
-  await ex.notify();
-  Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
-
-  await promiseRestartManager();
-});
-
-add_task(async function test_fetchInvalid() {
-  await removeCacheFile();
-
-  Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
-  let ex = new Experiments.Experiments(gPolicy);
-  await ex.updateManifest();
-  Assert.notEqual(ex._experiments.size, 0, "There should be experiments");
-
-  Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "invalid.manifest");
-  await ex.updateManifest();
-  Assert.notEqual(ex._experiments.size, 0, "There should still be experiments: fetch failure shouldn't remove them.");
-
-  await promiseRestartManager();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_nethang_bug1012924.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.import("resource:///modules/experiments/Experiments.jsm");
-
-const MANIFEST_HANDLER         = "manifests/handler";
-
-add_task(async 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!
-    });
-
-  registerCleanupFunction(() => 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);
-    });
-  await experiments.uninit();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_previous_provider.js
+++ /dev/null
@@ -1,173 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/Promise.jsm");
-ChromeUtils.import("resource:///modules/experiments/Experiments.jsm");
-ChromeUtils.import("resource://testing-common/httpd.js");
-
-var gDataRoot;
-var gHttpServer;
-var gManifestObject;
-
-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();
-  });
-  registerCleanupFunction(() => 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(async 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 = await 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()],
-      },
-    ],
-  };
-
-  await e.updateManifest();
-
-  deferred = Promise.defer();
-  provider.getAddonsByTypes(["experiment"], (addons) => {
-    deferred.resolve(addons);
-  });
-  experimentAddons = await deferred.promise;
-  Assert.equal(experimentAddons.length, 0, "Still no previous experiment.");
-
-  let experiments = await 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));
-  await e.updateManifest();
-
-  experiments = await 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 = await 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 = await 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 = await 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(async 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());
-  await e.updateManifest();
-
-  let experiments = await e.getExperiments();
-  Assert.equal(experiments.length, 2, "2 experiments known.");
-
-  let deferred = Promise.defer();
-  provider.getAddonsByTypes(["experiment"], (addons) => {
-    deferred.resolve(addons);
-  });
-  let experimentAddons = await deferred.promise;
-  Assert.equal(experimentAddons.length, 1, "1 previous experiment.");
-
-  deferred = Promise.defer();
-  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
-    deferred.resolve(addons);
-  });
-  experimentAddons = await 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);
-    }
-  }
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_telemetry.js
+++ /dev/null
@@ -1,289 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource://testing-common/httpd.js");
-ChromeUtils.import("resource://gre/modules/TelemetryLog.jsm");
-var {TELEMETRY_LOG, Experiments} = ChromeUtils.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 = TELEMETRY_LOG;
-
-function checkEvent(event, id, data) {
-  info("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");
-    }
-  }
-}
-
-add_task(async 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();
-  });
-  registerCleanupFunction(() => 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,
-  });
-
-  await removeCacheFile();
-});
-
-// Test basic starting and stopping of experiments.
-
-add_task(async 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);
-
-  await experiments.updateManifest();
-  let list = await experiments.getExperiments();
-  Assert.equal(list.length, 0, "Experiment list should be empty.");
-
-  expectedLogLength += 2;
-  let log = TelemetryLog.entries();
-  info("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);
-
-  await experiments.updateManifest();
-  list = await 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);
-
-  await experiments.updateManifest();
-  list = await 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";
-
-  await experiments.updateManifest();
-  list = await 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;
-
-  await experiments.updateManifest();
-  list = await 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);
-
-  await experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
-  list = await 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));
-
-  await experiments.updateManifest();
-  list = await 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);
-
-  await experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
-  list = await 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));
-
-  await experiments.updateManifest();
-  list = await 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";
-
-  await experiments.updateManifest();
-  list = await 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.
-
-  await promiseRestartManager();
-  await removeCacheFile();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_telemetry_disabled.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-ChromeUtils.import("resource:///modules/experiments/Experiments.jsm");
-
-add_test(function test_experiments_activation() {
-  do_get_profile();
-  loadAddonManager();
-
-  Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
-  Services.telemetry.canRecordExtended = false;
-
-  let experiments = Experiments.instance();
-
-  Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
-
-  run_next_test();
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/test_upgrade.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-ChromeUtils.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(async function test_valid() {
-  let e = new Experiments.ExperimentEntry();
-  Assert.ok(e.initFromCacheData(cacheData));
-  Assert.ok(e.enabled);
-});
-
-add_task(async function test_upgrade() {
-  let e = new Experiments.ExperimentEntry();
-  delete cacheData._branch;
-  Assert.ok(e.initFromCacheData(cacheData));
-  Assert.ok(e.enabled);
-});
-
-add_task(async function test_missing() {
-  let e = new Experiments.ExperimentEntry();
-  delete cacheData._name;
-  Assert.ok(!e.initFromCacheData(cacheData));
-});
deleted file mode 100644
--- a/browser/experiments/test/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,30 +0,0 @@
-[DEFAULT]
-head = head.js
-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/extensions/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]
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -186,18 +186,16 @@
 @RESPATH@/browser/components/aboutdebugging-registration.js
 @RESPATH@/browser/components/aboutdebugging.manifest
 @RESPATH@/browser/components/aboutdevtools-registration.js
 @RESPATH@/browser/components/aboutdevtools.manifest
 @RESPATH@/browser/components/aboutdevtoolstoolbox-registration.js
 @RESPATH@/browser/components/aboutdevtoolstoolbox.manifest
 @RESPATH@/browser/components/nsAboutCapabilities.js
 @RESPATH@/browser/components/aboutcapabilities.manifest
-@RESPATH@/browser/components/Experiments.manifest
-@RESPATH@/browser/components/ExperimentsService.js
 @RESPATH@/browser/components/aboutNewTabService.js
 @RESPATH@/browser/components/NewTabComponents.manifest
 @RESPATH@/browser/components/EnterprisePolicies.js
 @RESPATH@/browser/components/EnterprisePoliciesContent.js
 @RESPATH@/browser/components/EnterprisePolicies.manifest
 @RESPATH@/components/Downloads.manifest
 @RESPATH@/components/DownloadLegacy.js
 @RESPATH@/components/PageThumbsComponents.manifest
--- a/browser/moz.build
+++ b/browser/moz.build
@@ -9,17 +9,16 @@ CONFIGURE_SUBST_FILES += ['installer/Mak
 SPHINX_TREES['browser'] = 'docs'
 
 with Files('docs/**'):
     SCHEDULES.exclusive = ['docs']
 
 DIRS += [
     'base',
     'components',
-    'experiments',
     'fonts',
     'locales',
     'modules',
     'themes',
     'extensions',
 ]
 
 DIRS += [
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 37.0
+Version 38.0
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-36...release-37
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-37...release-38
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.0
 - babel-preset-react @6.24.1
 - react @16.2.0
 - react-dom @16.2.0
 - webpack @3.11.0
--- a/devtools/client/debugger/new/debugger.css
+++ b/devtools/client/debugger/new/debugger.css
@@ -1359,18 +1359,19 @@ html[dir="rtl"] .tree-node img.arrow {
 
 .sources-list .managed-tree .tree .node img.blackBox {
   mask: url("chrome://devtools/skin/images/debugger/blackBox.svg") no-repeat;
   mask-size: 100%;
   background-color: var(--theme-highlight-blue);
   width: 13px;
   height: 13px;
   display: inline-block;
-  margin-inline-end: 5px;
-  margin-bottom: -2px;
+  margin-inline-end: 6px;
+  margin-inline-start: 1px;
+  margin-top: 2px;
 }
 
 .sources-list .managed-tree .tree .node.focused img {
   background-color: white;
 }
 
 .theme-dark .sources-list .managed-tree .tree .node:not(.focused) img.blackBox {
   background-color: var(--theme-comment);
@@ -1943,17 +1944,16 @@ html .toggle-button.end.vertical svg {
   --null-color: var(--theme-comment);
   --object-color: var(--theme-highlight-blue);
   --caption-color: var(--theme-highlight-blue);
   --location-color: var(--theme-comment);
   --source-link-color: var(--theme-highlight-blue);
   --node-color: var(--theme-highlight-purple);
   --reference-color: var(--theme-highlight-blue);
   --comment-node-color: var(--theme-comment);
-  --stack-function-color: var(--theme-highlight-red);
 }
 
 .theme-firebug {
   --number-color: #000088;
   --string-color: #FF0000;
   --null-color: #787878;
   --object-color: DarkGreen;
   --caption-color: #444444;
@@ -2005,37 +2005,60 @@ html .toggle-button.end.vertical svg {
   cursor: pointer;
 }
 
 .objectBox-string a:hover {
   text-decoration: underline;
 }
 
 .objectBox-function,
-.objectBox-stackTrace,
 .objectBox-profile {
   color: var(--object-color);
 }
 
+.objectBox-stackTrace {
+  color: var(--error-color);
+}
+
 .objectBox-stackTrace-grid {
   display: inline-grid;
   grid-template-columns: auto auto;
   margin-top: 3px;
 }
 
 .objectBox-stackTrace-fn::before {
   content: "\3BB"; /* The "lambda" symbol */
-  color: var(--theme-body-color);
   display: inline-block;
   margin: 0 0.3em;
 }
 
 .objectBox-stackTrace-fn {
-  color: var(--stack-function-color);
+  color: var(--console-output-color);
   padding-inline-start: 17px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-inline-end: 5px;
+}
+
+.objectBox-stackTrace-location {
+  color: var(--frame-link-source, currentColor);
+  direction: rtl;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  text-align: end;
+}
+
+.objectBox-stackTrace-location:hover {
+  text-decoration: underline;
+}
+
+.objectBox-stackTrace-location {
+  cursor: pointer;
 }
 
 .objectBox-Location,
 .location {
   color: var(--location-color);
 }
 
 .objectBox-null,
@@ -2954,16 +2977,20 @@ html[dir="rtl"] .breakpoints-list .break
 .CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-code,
 .CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-scroll {
   cursor: default;
 }
 /* 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/>. */
 
+.watch-expressions-pane .plus {
+  margin-top: -2px;
+}
+
 .expression-input-form {
   width: 100%;
 }
 
 .input-expression {
   width: 100%;
   margin: 0;
   border: 1px;
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -2321,18 +2321,17 @@ var _path = __webpack_require__(1393);
 var _url = __webpack_require__(334);
 
 var _sourcesTree = __webpack_require__(1442);
 
 const sourceTypes = exports.sourceTypes = {
   coffee: "coffeescript",
   js: "javascript",
   jsx: "react",
-  ts: "typescript",
-  css: "css"
+  ts: "typescript"
 };
 
 /**
  * Trims the query part or reference identifier of a url string, if necessary.
  *
  * @memberof utils/source
  * @static
  */
@@ -7068,28 +7067,19 @@ class ManagedTree extends _react.Compone
   }
 
   highlightItem(highlightItems) {
     const { expanded } = this.state;
     // This file is visible, so we highlight it.
     if (expanded.has(this.props.getPath(highlightItems[0]))) {
       this.focusItem(highlightItems[0]);
     } else {
-      // Look at folders starting from the top-level and expand all the items
-      // which lie in the path of the item to be highlighted
-      highlightItems.reverse();
-      let index = highlightItems.findIndex(item => !expanded.has(this.props.getPath(item)));
-
-      if (this.props.autoExpandOnHighlight) {
-        while (index < highlightItems.length - 1) {
-          this.setExpanded(highlightItems[index], true, false);
-          index++;
-        }
-      }
-
+      // Look at folders starting from the top-level until finds a
+      // closed folder and highlights this folder
+      const index = highlightItems.reverse().findIndex(item => !expanded.has(this.props.getPath(item)));
       this.focusItem(highlightItems[index]);
     }
   }
 
   render() {
     const { expanded, focusedItem } = this.state;
     return _react2.default.createElement(
       "div",
@@ -10213,17 +10203,17 @@ var _initialiseProps = function () {
       className: (0, _classnames2.default)("result-item", {
         selected: index === selected
       })
     };
 
     return _react2.default.createElement(
       "li",
       props,
-      _react2.default.createElement(
+      item.icon && _react2.default.createElement(
         "div",
         null,
         _react2.default.createElement("img", { className: item.icon })
       ),
       _react2.default.createElement(
         "div",
         { id: `${item.id}-title`, className: "title" },
         item.title
@@ -10594,17 +10584,18 @@ function supportsObject(object, noGrip =
 
   return getGripType(object, noGrip) == "string";
 }
 
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(StringRep),
-  supportsObject
+  supportsObject,
+  isLongString
 };
 
 /***/ }),
 
 /***/ 1448:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
@@ -16989,17 +16980,16 @@ class SourcesTree extends _react.Compone
 
     if (isEmpty && !isCustomRoot) {
       return this.renderEmptyElement(L10N.getStr("sources.noSourcesAvailable"));
     }
 
     const treeProps = {
       autoExpandAll: false,
       autoExpandDepth: expanded ? 0 : 1,
-      autoExpandOnHighlight: true,
       expanded,
       getChildren: item => (0, _sourcesTree.nodeHasChildren)(item) ? item.contents : [],
       getParent: item => parentMap.get(item),
       getPath: this.getPath,
       getRoots: roots,
       highlightItems,
       itemHeight: 21,
       key: isEmpty ? "empty" : "full",
@@ -18649,17 +18639,20 @@ class Popup extends _react.Component {
 
   async componentWillMount() {
     const {
       value,
       expression,
       setPopupObjectProperties,
       popupObjectProperties
     } = this.props;
-    const root = createNode(null, expression, expression, { value });
+    const root = createNode({
+      name: expression,
+      contents: { value }
+    });
 
     if (!nodeIsPrimitive(root) && value && value.actor && !popupObjectProperties[value.actor]) {
       const onLoadItemProperties = loadItemProperties(root, _firefox.createObjectClient);
       if (onLoadItemProperties !== null) {
         const properties = await onLoadItemProperties;
         setPopupObjectProperties(value, properties);
       }
     }
@@ -18763,17 +18756,17 @@ class Popup extends _react.Component {
       _react2.default.createElement(_Svg2.default, { name: "immutable", className: "immutable-logo" }),
       _react2.default.createElement(
         "h3",
         null,
         immutableHeader
       )
     );
 
-    const roots = [createNode(null, "entries", "entries", { value: immutable.entries })];
+    const roots = [createNode({ name: "entries", contents: { value: immutable.entries } })];
 
     return _react2.default.createElement(
       "div",
       { className: "preview-popup" },
       header,
       this.renderObjectInspector(roots)
     );
   }
@@ -20470,20 +20463,22 @@ module.exports = {
 const PropTypes = __webpack_require__(20);
 // Utils
 const {
   getGripType,
   isGrip,
   wrapRender
 } = __webpack_require__(1353);
 const { cleanFunctionName } = __webpack_require__(1573);
+const { isLongString } = __webpack_require__(1447);
 const { MODE } = __webpack_require__(1357);
 
 const dom = __webpack_require__(1758);
 const { span } = dom;
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
 
 /**
  * Renders Error objects.
  */
 ErrorRep.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
   mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
@@ -20513,17 +20508,17 @@ function ErrorRep(props) {
 
   if (props.mode === MODE.TINY) {
     content.push(name);
   } else {
     content.push(`${name}: "${preview.message}"`);
   }
 
   if (preview.stack && props.mode !== MODE.TINY) {
-    content.push("\n", getStacktraceElements(preview));
+    content.push("\n", getStacktraceElements(props, preview));
   }
 
   return span({
     "data-link-actor-id": object.actor,
     className: "objectBox-stackTrace"
   }, content);
 }
 
@@ -20538,52 +20533,86 @@ function ErrorRep(props) {
  *
  * Into a column layout:
  *
  * semicolon  (<anonymous>:8:10)
  * jkl        (<anonymous>:5:10)
  * asdf       (<anonymous>:2:10)
  *            (<anonymous>:11:1)
  */
-function getStacktraceElements(preview) {
+function getStacktraceElements(props, preview) {
   const stack = [];
-  preview.stack.split("\n").forEach((line, index) => {
-    if (!line) {
+  if (!preview.stack) {
+    return stack;
+  }
+
+  const isStacktraceALongString = isLongString(preview.stack);
+  const stackString = isStacktraceALongString ? preview.stack.initial : preview.stack;
+
+  stackString.split("\n").forEach((frame, index) => {
+    if (!frame) {
       // Skip any blank lines
       return;
     }
 
     let functionName;
     let location;
 
     // Given the input: "functionName@scriptLocation:2:100"
     // Result:
     // ["functionName@scriptLocation:2:100", "functionName", "scriptLocation:2:100"]
-    const result = line.match(/^(.*)@(.*)$/);
+    const result = frame.match(/^(.*)@(.*)$/);
     if (result && result.length === 3) {
       functionName = result[1];
 
       // If the resource was loaded by base-loader.js, the location looks like:
       // resource://devtools/shared/base-loader.js -> resource://path/to/file.js .
       // What's needed is only the last part after " -> ".
       location = result[2].split(" -> ").pop();
     }
 
     if (!functionName) {
       functionName = "<anonymous>";
     }
 
+    let onLocationClick;
+    // Given the input: "scriptLocation:2:100"
+    // Result:
+    // ["scriptLocation:2:100", "scriptLocation", "2", "100"]
+    const locationParts = location.match(/^(.*):(\d+):(\d+)$/);
+    if (props.onViewSourceInDebugger && location && !IGNORED_SOURCE_URLS.includes(locationParts[1]) && locationParts) {
+      let [, url, line, column] = locationParts;
+      onLocationClick = e => {
+        // Don't trigger ObjectInspector expand/collapse.
+        e.stopPropagation();
+        props.onViewSourceInDebugger({
+          url,
+          line: Number(line),
+          column: Number(column)
+        });
+      };
+    }
+
     stack.push(span({
       key: "fn" + index,
       className: "objectBox-stackTrace-fn"
     }, cleanFunctionName(functionName)), span({
       key: "location" + index,
-      className: "objectBox-stackTrace-location"
-    }, ` (${location})`));
-  });
+      className: "objectBox-stackTrace-location",
+      onClick: onLocationClick,
+      title: onLocationClick ? "View source in debugger → " + location : undefined
+    }, location));
+  });
+
+  if (isStacktraceALongString) {
+    // Remove the last frame (i.e. 2 last elements in the array, the function name and the
+    // location) which is certainly incomplete.
+    // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833 is fixed.
+    stack.splice(-2);
+  }
 
   return span({
     key: "stack",
     className: "objectBox-stackTrace-grid"
   }, stack);
 }
 
 // Registration
@@ -21027,16 +21056,21 @@ module.exports = {
 /* 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/. */
 
 const { createElement, createFactory, PureComponent } = __webpack_require__(0);
 const { Provider } = __webpack_require__(3592);
 const ObjectInspector = createFactory(__webpack_require__(3615));
 const createStore = __webpack_require__(3618);
+const Utils = __webpack_require__(1938);
+const {
+  renderRep,
+  shouldRenderRootsInReps
+} = Utils;
 
 class OI extends PureComponent {
 
   constructor(props) {
     super(props);
     this.store = createStore(props);
   }
 
@@ -21044,17 +21078,23 @@ class OI extends PureComponent {
     return this.store;
   }
 
   render() {
     return createElement(Provider, { store: this.store }, ObjectInspector(this.props));
   }
 }
 
-module.exports = OI;
+module.exports = props => {
+  let { roots } = props;
+  if (shouldRenderRootsInReps(roots)) {
+    return renderRep(roots[0], props);
+  }
+  return new OI(props);
+};
 
 /***/ }),
 
 /***/ 1586:
 /***/ (function(module, exports, __webpack_require__) {