Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Fri, 04 May 2018 21:07:31 +0300
changeset 473088 7ba78b394fcbafc82a925ef3ca8b0ba3ef272b47
parent 473087 4065e9400f2bdf7715ac87e5721829abfe83e51e (current diff)
parent 473069 a91ca6e5ca820b53fad9bb98b256934353f5217a (diff)
child 473089 64703ce328eaa651f03f8ae7346c671a796c3207
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/inspector/inspector.js
devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
devtools/client/shared/webpack/shims/unicode-url-stub.js
devtools/shared/old-event-emitter.js
devtools/shared/tests/mochitest/test_eventemitter_basic.html
devtools/shared/tests/unit/test_old_eventemitter_basic.js
dom/media/test/file_autoplay_policy_key_blacklist.html
dom/media/test/test_autoplay_policy_key_blacklist.html
taskcluster/ci/release-bouncer-aliases/kind.yml
taskcluster/ci/release-sign-and-push-langpacks/kind.yml
taskcluster/taskgraph/transforms/job/buildbot.py
taskcluster/taskgraph/util/bbb_validation.py
testing/profiles/common/user.js
testing/profiles/prefs_general.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1453317 - Update VS2017 used for builds in automation to version 15.6.6.
+Bug 1451159 - Moved testing/profiles/prefs_general.js (prevents symlinked file does not exist)
--- a/browser/base/content/test/general/browser_audioTabIcon.js
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -71,18 +71,19 @@ async function pause(tab, options) {
       await awaitDOMAudioPlaybackStopped;
       ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped");
     }
 
     await wait_for_tab_playing_event(tab, false);
     ok(!tab.hasAttribute("soundplaying"), "The tab should not have the soundplaying attribute after the timeout has resolved");
   } finally {
     // Make sure other tests don't timeout if an exception gets thrown above.
-    // Need to use setIntPref instead of clearUserPref because prefs_general.js
-    // overrides the default value to help this and other tests run faster.
+    // Need to use setIntPref instead of clearUserPref because
+    // testing/profiles/common/user.js overrides the default value to help this and
+    // other tests run faster.
     Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS);
   }
 }
 
 async function hide_tab(tab) {
   let tabHidden = BrowserTestUtils.waitForEvent(tab, "TabHide");
   gBrowser.hideTab(tab);
   return tabHidden;
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -18,16 +18,22 @@
 
 namespace mozilla {
 namespace browser {
 
 NS_IMPL_ISUPPORTS(AboutRedirector, nsIAboutModule)
 
 bool AboutRedirector::sNewTabPageEnabled = false;
 
+static const uint32_t ACTIVITY_STREAM_FLAGS =
+  nsIAboutModule::ALLOW_SCRIPT |
+  nsIAboutModule::ENABLE_INDEXED_DB |
+  nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+  nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
+
 struct RedirEntry {
   const char* id;
   const char* url;
   uint32_t flags;
 };
 
 /*
   Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
@@ -73,31 +79,21 @@ static const RedirEntry kRedirMap[] = {
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   // Actual activity stream URL for home and newtab are set in channel creation
-  // Linkable because of indexeddb use (bug 1228118)
-  { "home", "about:blank",
-    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
-    nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
-    nsIAboutModule::ALLOW_SCRIPT |
-    nsIAboutModule::MAKE_LINKABLE |
-    nsIAboutModule::ENABLE_INDEXED_DB },
+  { "home", "about:blank", ACTIVITY_STREAM_FLAGS },
+  { "newtab", "about:blank", ACTIVITY_STREAM_FLAGS },
   { "library", "chrome://browser/content/aboutLibrary.xhtml",
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT },
-  { "newtab", "about:blank",
-    nsIAboutModule::ENABLE_INDEXED_DB |
-    nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
-    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
-    nsIAboutModule::ALLOW_SCRIPT },
   { "preferences", "chrome://browser/content/preferences/in-content/preferences.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
     nsIAboutModule::ALLOW_SCRIPT },
   { "reader", "chrome://global/content/reader/aboutReader.html",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
--- a/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser_policy_disable_fxscreenshots.js
+++ b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser_policy_disable_fxscreenshots.js
@@ -11,19 +11,19 @@ async function checkScreenshots(shouldBe
   }, "Expecting screenshots to be " + shouldBeEnabled);
 }
 
 add_task(async function test_disable_firefox_screenshots() {
   // Dynamically toggling the PREF_DISABLE_FX_SCREENSHOTS is very finicky, because
   // that pref is being watched, and it makes the Firefox Screenshots system add-on
   // to start or stop, causing intermittency.
   //
-  // Firefox Screenshots is disabled by default on tests (in prefs_general.js).
-  // What we do here to test this policy is to enable it on this specific
-  // test folder (through browser.ini) and then we let the policy engine
-  // be responsible for disabling Firefox Screenshots in this case.
+  // Firefox Screenshots is disabled by default on tests (in
+  // testing/profiles/common/user.js). What we do here to test this policy is to enable
+  // it on this specific test folder (through browser.ini) and then we let the policy
+  // engine be responsible for disabling Firefox Screenshots in this case.
 
   is(Services.prefs.getBoolPref(PREF_DISABLE_FX_SCREENSHOTS), true, "Screenshots pref is disabled");
 
   await BrowserTestUtils.withNewTab("data:text/html,Test", async function() {
     await checkScreenshots(false);
   });
 });
--- a/browser/components/preferences/connection.xul
+++ b/browser/components/preferences/connection.xul
@@ -45,18 +45,18 @@
     </hbox>
 
     <groupbox>
       <caption><label data-l10n-id="connection-proxy-configure" /></caption>
 
       <radiogroup id="networkProxyType" preference="network.proxy.type"
                   onsyncfrompreference="return gConnectionsDialog.readProxyType();">
         <radio value="0" data-l10n-id="connection-proxy-option-no" />
-        <radio value="4" data-l10n-id="connection-proxy-option-system" />
-        <radio value="5" data-l10n-id="connection-proxy-option-auto" id="systemPref" hidden="true" />
+        <radio value="4" data-l10n-id="connection-proxy-option-auto" />
+        <radio value="5" data-l10n-id="connection-proxy-option-system" id="systemPref" hidden="true" />
         <radio value="1" data-l10n-id="connection-proxy-option-manual"/>
         <grid class="indent" flex="1">
           <columns>
             <column/>
             <column flex="1"/>
           </columns>
           <rows>
             <row align="center">
--- a/browser/extensions/pocket/test/head.js
+++ b/browser/extensions/pocket/test/head.js
@@ -21,17 +21,17 @@ function promisePocketEnabled() {
 
 function promisePocketDisabled() {
   if (Services.prefs.getPrefType("extensions.pocket.enabled") == Services.prefs.PREF_INVALID ||
       !Services.prefs.getBoolPref("extensions.pocket.enabled")) {
     info("pocket-button already disabled");
     return Promise.resolve(true);
   }
   info("reset pocket enabled pref");
-  // testing/profiles/prefs_general.js uses user_pref to disable pocket, set
+  // testing/profiles/common/user.js uses user_pref to disable pocket, set
   // back to false.
   Services.prefs.setBoolPref("extensions.pocket.enabled", false);
   return BrowserTestUtils.waitForCondition(() => {
     return !PageActions.actionForID("pocket");
   }).then(() => {
     // wait for a full unload of pocket
     return BrowserTestUtils.waitForCondition(() => {
       return !window.hasOwnProperty("pktUI");
--- a/browser/locales/searchplugins/heureka-cz.xml
+++ b/browser/locales/searchplugins/heureka-cz.xml
@@ -1,15 +1,15 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Heuréka</ShortName>
-<Description>Vyhledávání na Heuréka.cz</Description>
+<ShortName>Heureka</ShortName>
+<Description>Vyhledávání na Heureka.cz</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAA9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//769f/59/X/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/+/y9f8mjP7/lMP5//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/09fX//fn1/6XM+P97t/r/ir/6/9jm9v8ahf7/AHb//7fV+P/19fX/9fX1//X19f/19fX/9fX1//X19f/09fX/0eL2/w1///8AdP//NZP9/wJ5//8AdP//A3n//7HT+P/39vX/9fX1//X19f/19fX/9fX1//X19f/19fX/9PT1/w5///8wkf3/8vP1//Dz9f///vT/gbr6/wBy///I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/5fF+f8Ad///+ff1/8rf9/8Ab///nMj4///79f85lf3/TZ/8//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f98t/r/N5T9//n39f8Nfv//HIb+/wR6///y9PX/g7v6/xOC////+/T/9fX1//X19f/19fX/9fX1//X19f/19fX/grr6/xqF/v/y9PX/Xqj7//359f8bhv7/W6b8/3Cx+v8pjf3//vr1//X19f/19fX/9fX1//X19f/19fX/9fX1/9fm9v8AcP//mcb5//759f/09fX/wdv3/wBz//8Lfv//g7v6//b19f/19fX/9fX1//X19f/19fX/9fX1//X19f/59/X/eLX6/wBw//9Ypfz/nsn5/3W0+v8Ge///LY/9//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//z59f+s0Pj/MpH9/xOC//8giP7/f7n6//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//359f//+/T///r1//b29f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA9fX1gPX19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19YD19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/5vH+f9Amf3/bbD7//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f+bx/n/BHr//wR6//8Eev//9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/m8f5/wR6//8Eev//BHr//xOC/v/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/X5vb/bbD7/zGR/f8Eev//BHr//wR6//9Amf3/m8f5/4y/+f8Eev//BHr//wR6//8Tgv7/yN73//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/bbD7/wR6//8Eev//BHr//wR6//8Eev//BHr//wR6//8Eev//BHr//wR6//8Eev//E4L+/8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/zGR/f8Eev//BHr//wR6//8iif7/Xqj7/324+v9Amf3/BHr//wR6//8Eev//BHr//xOC/v/I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9tsPv/BHr//wR6//8Tgv7/m8f5//X19f/19fX/9fX1//X19f/m7fb/bbD7/wR6//8Eev//BHr//8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/udb4/wR6//8Eev//E4L+/8je9//19fX/9fX1/9fm9v+51vj/9fX1//X19f/19fX/m8f5/wR6//8Eev//QJn9//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9eqPv/BHr//wR6//+51vj/9fX1//X19f/X5vb/E4L+/wR6//99uPr/9fX1//X19f/19fX/QJn9/wR6//8Eev//yN73//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/xOC/v8Eev//MZH9//X19f/19fX/9fX1/zGR/f8Eev//BHr//xOC/v/X5vb/9fX1//X19f+51vj/BHr//wR6//99uPr/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/BHr//wR6//9tsPv/9fX1//X19f9tsPv/BHr//wR6//8Eev//BHr//0+g/P/19fX/9fX1//X19f8Eev//BHr//0CZ/f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f8Eev//BHr//324+v/19fX/udb4/wR6//8Eev//MZH9/0+g/P8Eev//BHr//6rP+P/19fX/9fX1/xOC/v8Eev//QJn9//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/wR6//8Eev//bbD7//X19f+bx/n/BHr//xOC/v/I3vf/1+b2/xOC/v8Eev//Ion+/+bt9v/19fX/BHr//wR6//9PoPz/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/Ion+/wR6//8iif7/9fX1//X19f/I3vf/1+b2//X19f/19fX/jL/5/wR6//8Eev//bbD7/6rP+P8Eev//BHr//324+v/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9tsPv/BHr//wR6//+Mv/n/9fX1//X19f/19fX/9fX1//X19f/19fX/MZH9/wR6//8Eev//MZH9/wR6//8Eev//1+b2//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/9fm9v8Eev//BHr//xOC/v+51vj/9fX1//X19f/19fX/9fX1//X19f+51vj/BHr//wR6//8Eev//BHr//0CZ/f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/4y/+f8Eev//BHr//wR6//99uPr/5u32//X19f/19fX/9fX1/7nW+P8iif7/BHr//wR6//8Tgv7/1+b2//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/16o+/8Eev//BHr//wR6//8Eev//MZH9/0CZ/f8Tgv7/BHr//wR6//8Eev//E4L+/8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/6rP+P8Tgv7/BHr//wR6//8Eev//BHr//wR6//8Eev//BHr//0CZ/f/X5vb/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/+bt9v+bx/n/T6D8/0CZ/f9Amf3/QJn9/22w+//I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fWA9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.heureka.cz/direct/firefox/autocompleter.php">
 	<Param name="query" value="{searchTerms}"/>
 </Url>
 <Url type="text/html" method="GET" template="https://www.heureka.cz/" resultdomain="heureka.cz">
 	<Param name="h[fraze]" value="{searchTerms}"/>
 	<Param name="utm_source" value="firefox-search"/>
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -693,18 +693,18 @@ html|*.urlbar-input:-moz-lwtheme::placeh
   min-height: 0;
   padding: 0;
 }
 
 %include ../shared/tabs.inc.css
 
 @media (-moz-windows-default-theme: 0) {
   /* For high contrast themes. */
-  tabbrowser,
-  :root[privatebrowsingmode=temporary] tabbrowser {
+  #tabbrowser-tabpanels,
+  :root[privatebrowsingmode=temporary] #tabbrowser-tabpanels {
     background-color: -moz-default-background-color;
   }
 }
 
 /* tabbrowser-tab focus ring */
 .tabbrowser-tab:focus > .tab-stack > .tab-content {
   outline: 1px dotted;
   outline-offset: -6px;
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -30,18 +30,16 @@ if os.path.isdir(mozbase):
             sys.path.append(package_path)
 
 import mozcrash
 from mozscreenshot import printstatus, dump_screen
 
 
 # ---------------------------------------------------------------
 
-_DEFAULT_PREFERENCE_FILE = os.path.join(SCRIPT_DIR, 'prefs_general.js')
-
 _DEFAULT_WEB_SERVER = "127.0.0.1"
 _DEFAULT_HTTP_PORT = 8888
 _DEFAULT_SSL_PORT = 4443
 _DEFAULT_WEBSOCKET_PORT = 9988
 
 #expand _DIST_BIN = __XPC_BIN_PATH__
 #expand _IS_WIN32 = len("__WIN32__") != 0
 #expand _IS_MAC = __IS_MAC__ != 0
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -50,16 +50,17 @@ MACH_MODULES = [
     'testing/awsy/mach_commands.py',
     'testing/firefox-ui/mach_commands.py',
     'testing/geckodriver/mach_commands.py',
     'testing/mach_commands.py',
     'testing/marionette/mach_commands.py',
     'testing/mochitest/mach_commands.py',
     'testing/mozharness/mach_commands.py',
     'testing/raptor/mach_commands.py',
+    'testing/tps/mach_commands.py',
     'testing/talos/mach_commands.py',
     'testing/web-platform/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
     'tools/compare-locales/mach_commands.py',
     'tools/docs/mach_commands.py',
     'tools/lint/mach_commands.py',
     'tools/mach_commands.py',
     'tools/power/mach_commands.py',
--- a/build/pgo/profileserver.py
+++ b/build/pgo/profileserver.py
@@ -1,24 +1,24 @@
 #!/usr/bin/python
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import json
 import os
 
 from buildconfig import substs
 from mozbuild.base import MozbuildObject
 from mozfile import TemporaryDirectory
 from mozhttpd import MozHttpd
 from mozprofile import FirefoxProfile, Profile, Preferences
 from mozprofile.permissions import ServerLocations
 from mozrunner import FirefoxRunner, CLI
+from six import string_types
 
 PORT = 8888
 
 PATH_MAPPINGS = {
     '/js-input/webkit/PerformanceTests': 'third_party/webkit/PerformanceTests',
 }
 
 
@@ -39,29 +39,30 @@ if __name__ == '__main__':
     locations = ServerLocations()
     locations.add_host(host='127.0.0.1',
                        port=PORT,
                        options='primary,privileged')
 
     with TemporaryDirectory() as profilePath:
         # TODO: refactor this into mozprofile
         prefpath = os.path.join(
-            build.topsrcdir, "testing", "profiles", "prefs_general.js")
+            build.topsrcdir, "testing", "profiles", "common", "user.js")
         overridepath = os.path.join(
             build.topsrcdir, "build", "pgo", "prefs_override.js")
 
         prefs = {}
         prefs.update(Preferences.read_prefs(prefpath))
         prefs.update(Preferences.read_prefs(overridepath))
 
         interpolation = {"server": "%s:%d" % httpd.httpd.server_address,
                          "OOP": "false"}
-        prefs = json.loads(json.dumps(prefs) % interpolation)
-        for pref in prefs:
-            prefs[pref] = Preferences.cast(prefs[pref])
+        for k, v in prefs.items():
+            if isinstance(v, string_types):
+                v = v.format(**interpolation)
+            prefs[k] = Preferences.cast(v)
 
         profile = FirefoxProfile(profile=profilePath,
                                  preferences=prefs,
                                  addons=[os.path.join(
                                      build.topsrcdir, 'tools', 'quitter', 'quitter@mozilla.org.xpi')],
                                  locations=locations)
 
         env = os.environ.copy()
new file mode 100644
--- /dev/null
+++ b/build/sparse-profiles/tps
@@ -0,0 +1,5 @@
+%include build/sparse-profiles/mach
+
+[include]
+path:services/sync/tps/
+path:testing/tps/
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -38,46 +38,48 @@ class MachCommands(MachCommandBase):
         conditions=[conditions.is_firefox, is_valgrind_build],
         description='Run the Valgrind test job (memory-related errors).')
     @CommandArgument('--suppressions', default=[], action='append',
         metavar='FILENAME',
         help='Specify a suppression file for Valgrind to use. Use '
             '--suppression multiple times to specify multiple suppression '
             'files.')
     def valgrind_test(self, suppressions):
-        import json
         import sys
         import tempfile
 
         from mozbuild.base import MozbuildObject
         from mozfile import TemporaryDirectory
         from mozhttpd import MozHttpd
         from mozprofile import FirefoxProfile, Preferences
         from mozprofile.permissions import ServerLocations
         from mozrunner import FirefoxRunner
         from mozrunner.utils import findInPath
+        from six import string_types
         from valgrind.output_handler import OutputHandler
 
         build_dir = os.path.join(self.topsrcdir, 'build')
 
         # XXX: currently we just use the PGO inputs for Valgrind runs.  This may
         # change in the future.
         httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo'))
         httpd.start(block=False)
 
         with TemporaryDirectory() as profilePath:
             #TODO: refactor this into mozprofile
-            prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js')
+            prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'common', 'user.js')
             prefs = {}
             prefs.update(Preferences.read_prefs(prefpath))
-            interpolation = { 'server': '%s:%d' % httpd.httpd.server_address,
-                              'OOP': 'false'}
-            prefs = json.loads(json.dumps(prefs) % interpolation)
-            for pref in prefs:
-                prefs[pref] = Preferences.cast(prefs[pref])
+            interpolation = {
+                'server': '%s:%d' % httpd.httpd.server_address,
+            }
+            for k, v in prefs.items():
+                if isinstance(v, string_types):
+                    v = v.format(**interpolation)
+                prefs[k] = Preferences.cast(v)
 
             quitter = os.path.join(self.topsrcdir, 'tools', 'quitter', 'quitter@mozilla.org.xpi')
 
             locations = ServerLocations()
             locations.add_host(host='127.0.0.1',
                                port=httpd.httpd.server_port,
                                options='primary')
 
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
   service-workers/delay-sw.js
   service-workers/empty-sw.html
   service-workers/empty-sw.js
   service-workers/fetch-sw.html
   service-workers/fetch-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_addons_debug_bootstrapped.js]
 skip-if = coverage # Bug 1387827
 [browser_addons_debug_info.js]
 [browser_addons_debug_webextension.js]
 tags = webextensions
 [browser_addons_debug_webextension_inspector.js]
 tags = webextensions
--- a/devtools/client/accessibility/test/browser.ini
+++ b/devtools/client/accessibility/test/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_accessibility_context_menu_browser.js]
 [browser_accessibility_context_menu_inspector.js]
 [browser_accessibility_mutations.js]
 [browser_accessibility_reload.js]
 [browser_accessibility_sidebar.js]
 [browser_accessibility_tree.js]
 [browser_accessibility_tree_nagivation.js]
--- a/devtools/client/application/test/browser.ini
+++ b/devtools/client/application/test/browser.ini
@@ -4,12 +4,13 @@ subsuite = devtools
 support-files =
   head.js
   service-workers/dynamic-registration.html
   service-workers/empty-sw.js
   service-workers/scope-page.html
   service-workers/simple.html
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_application_panel_list-domain-workers.js]
 [browser_application_panel_list-several-workers.js]
 [browser_application_panel_list-single-worker.js]
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -12,16 +12,17 @@ support-files =
   doc_simple-canvas-transparent.html
   doc_webgl-bindings.html
   doc_webgl-enum.html
   doc_webgl-drawArrays.html
   doc_webgl-drawElements.html
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_canvas-actor-test-01.js]
 [browser_canvas-actor-test-02.js]
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
--- a/devtools/client/commandline/test/browser.ini
+++ b/devtools/client/commandline/test/browser.ini
@@ -117,13 +117,14 @@ skip-if = true # Bug 1093205 - Test does
 [browser_gcli_keyboard5.js]
 [browser_gcli_menu.js]
 [browser_gcli_node.js]
 [browser_gcli_resource.js]
 [browser_gcli_short.js]
 [browser_gcli_spell.js]
 [browser_gcli_split.js]
 [browser_gcli_telemetry.js]
+skip-if = true # Disabling: Monkeypatches telemetry and GCLI is soon to be removed.
 [browser_gcli_tokenize.js]
 [browser_gcli_tooltip.js]
 skip-if = true # Bug 1093205 - Test does not run in Firefox due to missing terminal
 [browser_gcli_types.js]
 [browser_gcli_union.js]
--- a/devtools/client/commandline/test/head.js
+++ b/devtools/client/commandline/test/head.js
@@ -9,16 +9,19 @@
 
 const TEST_BASE_HTTP = "http://example.com/browser/devtools/client/commandline/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/devtools/client/commandline/test/";
 
 var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 var flags = require("devtools/shared/flags");
 var { Task } = require("devtools/shared/task");
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 // Import the GCLI test helper
 var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this, "UTF-8");
 
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -101,17 +101,17 @@ const { require } = BrowserLoader({
 });
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineConstant(this, "require", require);
 const { SimpleListWidget } = require("resource://devtools/client/shared/widgets/SimpleListWidget.jsm");
 const { BreadcrumbsWidget } = require("resource://devtools/client/shared/widgets/BreadcrumbsWidget.jsm");
 const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 const { VariablesViewController, StackFrameUtils } = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 const { extend } = require("devtools/shared/extend");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const { ViewHelpers, WidgetMethods, setNamedTimeout,
         clearNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 
 // Use privileged promise in panel documents to prevent having them to freeze
 // during toolbox destruction. See bug 1402779.
 const Promise = require("Promise");
@@ -445,17 +445,17 @@ var DebuggerController = {
                             action.status === "done"),
       run: deferred.resolve
     });
     return deferred.promise;
   },
 
   waitForSourceShown: function (name) {
     const deferred = promise.defer();
-    window.on(EVENTS.SOURCE_SHOWN, function onShown(_, source) {
+    window.on(EVENTS.SOURCE_SHOWN, function onShown(source) {
       if (source.url.includes(name)) {
         window.off(EVENTS.SOURCE_SHOWN, onShown);
         deferred.resolve();
       }
     });
     return deferred.promise;
   },
 
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 skip-if = (os == 'linux' && debug && bits == 32)
 support-files =
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   examples/babel/polyfill-bundle.js
   examples/babel/fixtures/eval-source-maps/output.js
   examples/babel/fixtures/eval-source-maps/output.js.map
   examples/babel/fixtures/for-of/output.js
   examples/babel/fixtures/for-of/output.js.map
   examples/babel/fixtures/line-start-bindings-es6/output.js
   examples/babel/fixtures/line-start-bindings-es6/output.js.map
   examples/babel/fixtures/shadowed-vars/output.js
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -2,17 +2,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const promise = require("promise");
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 function DebuggerPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
   this._destroyer = null;
 
   this._view = this.panelWin.DebuggerView;
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -127,16 +127,17 @@ support-files =
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_addon-modules.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-modules-unpacked.js]
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -127,16 +127,17 @@ support-files =
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dbg_no-dangling-breakpoints.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_no-page-sources.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_on-pause-highlight.js]
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -14,17 +14,17 @@ Services.scriptloader.loadSubScript("chr
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 var { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
 var ObjectClient = require("devtools/shared/client/object-client");
 var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});
-var EventEmitter = require("devtools/shared/old-event-emitter");
+var EventEmitter = require("devtools/shared/event-emitter");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { Task } = require("devtools/shared/task");
 
 const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
 
 // Override promise with deprecated-sync-thenables
 promise = require("devtools/shared/deprecated-sync-thenables");
 
@@ -375,17 +375,17 @@ function waitForSourceAndCaretAndScopes(
 
 function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) {
   info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
 
   let deferred = promise.defer();
   let panelWin = aPanel.panelWin;
   let count = 0;
 
-  panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) {
+  panelWin.on(aEventName, function onEvent(...aArgs) {
     info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s).");
 
     if (count == aEventRepeat) {
       ok(true, "Enough '" + aEventName + "' panel events have been fired.");
       panelWin.off(aEventName, onEvent);
       deferred.resolve.apply(deferred, aArgs);
     }
   });
--- a/devtools/client/dom/test/browser.ini
+++ b/devtools/client/dom/test/browser.ini
@@ -2,12 +2,13 @@
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   page_array.html
   page_basic.html
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dom_array.js]
 [browser_dom_basic.js]
 [browser_dom_refresh.js]
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -42,16 +42,17 @@ support-files =
   serviceworker.js
   sjs_code_reload.sjs
   sjs_code_bundle_reload_map.sjs
   test_browser_toolbox_debugger.js
   !/devtools/client/debugger/new/test/mochitest/head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_browser_toolbox.js]
 skip-if = coverage # Bug 1387827
 [browser_browser_toolbox_debugger.js]
 skip-if = os == 'win' || debug # Bug 1282269, 1448084
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
--- a/devtools/client/framework/test/browser_toolbox_hosts_telemetry.js
+++ b/devtools/client/framework/test/browser_toolbox_hosts_telemetry.js
@@ -1,50 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* import-globals-from head.js */
+
 "use strict";
 
 const {Toolbox} = require("devtools/client/framework/toolbox");
 const {SIDE, BOTTOM, WINDOW} = Toolbox.HostType;
 
 const URL = "data:text/html;charset=utf8,browser_toolbox_hosts_telemetry.js";
 
-function getHostHistogram() {
-  return Services.telemetry.getHistogramById("DEVTOOLS_TOOLBOX_HOST");
-}
-
 add_task(async function() {
-  // Reset it to make counting easier
-  getHostHistogram().clear();
+  startTelemetry();
 
   info("Create a test tab and open the toolbox");
   let tab = await addTab(URL);
   let target = TargetFactory.forTab(tab);
   let toolbox = await gDevTools.showToolbox(target, "webconsole");
 
   await changeToolboxHost(toolbox);
   await checkResults();
-  await toolbox.destroy();
-
-  toolbox = target = null;
-  gBrowser.removeCurrentTab();
-
-  // Cleanup
-  getHostHistogram().clear();
 });
 
 async function changeToolboxHost(toolbox) {
   info("Switch toolbox host");
   await toolbox.switchHost(SIDE);
   await toolbox.switchHost(WINDOW);
   await toolbox.switchHost(BOTTOM);
   await toolbox.switchHost(SIDE);
   await toolbox.switchHost(WINDOW);
   await toolbox.switchHost(BOTTOM);
 }
 
 function checkResults() {
-  let counts = getHostHistogram().snapshot().counts;
-  is(counts[0], 3, "Toolbox HostType bottom has 3 successful entries");
-  is(counts[1], 2, "Toolbox HostType side has 2 successful entries");
-  is(counts[2], 2, "Toolbox HostType window has 2 successful entries");
+  // Check for:
+  //   - 3 "bottom" entries.
+  //   - 2 "side" entries.
+  //   - 2 "window" entries.
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", [3, 2, 2, 0, 0, 0, 0, 0, 0, 0], "array");
 }
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -1,14 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../shared/test/shared-head.js */
+/* import-globals-from ../../shared/test/telemetry-test-helpers.js */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 function toggleAllTools(state) {
   for (let [, tool] of gDevTools._tools) {
--- a/devtools/client/inspector/animation-old/test/browser.ini
+++ b/devtools/client/inspector/animation-old/test/browser.ini
@@ -19,16 +19,17 @@ support-files =
   doc_multiple_property_types.html
   doc_timing_combination_animation.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_animation_animated_properties_displayed.js]
 [browser_animation_animated_properties_for_delayed_starttime_animations.js]
 [browser_animation_animated_properties_path.js]
 [browser_animation_animated_properties_progress_indicator.js]
 [browser_animation_click_selects_animation.js]
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -8,16 +8,17 @@ support-files =
   doc_multi_keyframes.html
   doc_multi_timings.html
   doc_simple_animation.html
   head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_animation_animated-property-list.js]
 [browser_animation_animated-property-list_unchanged-items.js]
 [browser_animation_animated-property-name.js]
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
--- a/devtools/client/inspector/boxmodel/test/browser.ini
+++ b/devtools/client/inspector/boxmodel/test/browser.ini
@@ -4,16 +4,17 @@ subsuite = devtools
 support-files =
   doc_boxmodel_iframe1.html
   doc_boxmodel_iframe2.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_boxmodel.js]
 [browser_boxmodel_edit-position-visible-position-change.js]
 [browser_boxmodel_editablemodel.js]
 [browser_boxmodel_editablemodel_allproperties.js]
 disabled=too many intermittent failures (bug 1009322)
--- a/devtools/client/inspector/computed/test/browser.ini
+++ b/devtools/client/inspector/computed/test/browser.ini
@@ -9,16 +9,17 @@ support-files =
   doc_sourcemaps.css.map
   doc_sourcemaps.html
   doc_sourcemaps.scss
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_computed_browser-styles.js]
 [browser_computed_cycle_color.js]
 [browser_computed_getNodeInfo.js]
 [browser_computed_keybindings_01.js]
 [browser_computed_keybindings_02.js]
--- a/devtools/client/inspector/extensions/test/browser.ini
+++ b/devtools/client/inspector/extensions/test/browser.ini
@@ -3,12 +3,13 @@ tags = devtools
 subsuite = devtools
 support-files =
   head.js
   head_devtools_inspector_sidebar.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
-[browser_inspector_extension_sidebar.js]
\ No newline at end of file
+[browser_inspector_extension_sidebar.js]
--- a/devtools/client/inspector/fonts/test/browser.ini
+++ b/devtools/client/inspector/fonts/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   test_iframe.html
   ostrich-black.ttf
   ostrich-regular.ttf
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_fontinspector.js]
 [browser_fontinspector_copy-URL.js]
 skip-if = !e10s # too slow on !e10s, logging fully serialized actors (Bug 1446595)
 subsuite = clipboard
 [browser_fontinspector_edit-previews.js]
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -4,16 +4,17 @@ subsuite = devtools
 support-files =
   doc_iframe_reloaded.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_grids_accordion-state.js]
 [browser_grids_color-in-rules-grid-toggle.js]
 [browser_grids_display-setting-extend-grid-lines.js]
 [browser_grids_display-setting-show-grid-line-numbers.js]
 [browser_grids_display-setting-show-grid-areas.js]
--- a/devtools/client/inspector/grids/test/browser_grids_number-of-css-grids-telemetry.js
+++ b/devtools/client/inspector/grids/test/browser_grids_number-of-css-grids-telemetry.js
@@ -17,29 +17,33 @@ const TEST_URI2 = `
     }
   </style>
   <div id="grid">
     <div id="cell1">cell1</div>
     <div id="cell2">cell2</div>
   </div>
 `;
 
-const CSS_GRID_COUNT_HISTOGRAM_ID = "DEVTOOLS_NUMBER_OF_CSS_GRIDS_IN_A_PAGE";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI1));
 
+  startTelemetry();
+
   let { inspector } = await openLayoutView();
   let { store } = inspector;
 
   info("Navigate to TEST_URI2");
 
   let onGridListUpdate = waitUntilState(store, state => state.grids.length == 1);
   await navigateTo(inspector,
     "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI2));
   await onGridListUpdate;
 
-  let histogram = Services.telemetry.getHistogramById(CSS_GRID_COUNT_HISTOGRAM_ID);
-  let snapshot = histogram.snapshot();
+  checkResults();
+});
 
-  is(snapshot.counts[1], 1, "Got a count of 1 for 1 CSS Grid element seen.");
-  is(snapshot.sum, 1, "Got the correct sum.");
-});
+function checkResults() {
+  // Check for:
+  //   - 1 CSS Grid Element
+  checkTelemetry("DEVTOOLS_NUMBER_OF_CSS_GRIDS_IN_A_PAGE", "",
+    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "array");
+}
--- a/devtools/client/inspector/grids/test/head.js
+++ b/devtools/client/inspector/grids/test/head.js
@@ -1,13 +1,14 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 /* import-globals-from ../../../shared/test/shared-head.js */
+/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
 /* import-globals-from ../../test/head.js */
 "use strict";
 
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -2142,45 +2142,45 @@ Inspector.prototype = {
   /**
    * Copy a unique selector of the selected Node to the clipboard.
    */
   copyUniqueSelector: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyuniquecssselector");
+    this.telemetry.logScalar("devtools.copy.unique.css.selector.opened", 1);
     this.selection.nodeFront.getUniqueSelector().then(selector => {
       clipboardHelper.copyString(selector);
     }).catch(console.error);
   },
 
   /**
    * Copy the full CSS Path of the selected Node to the clipboard.
    */
   copyCssPath: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyfullcssselector");
+    this.telemetry.logScalar("devtools.copy.full.css.selector.opened", 1);
     this.selection.nodeFront.getCssPath().then(path => {
       clipboardHelper.copyString(path);
     }).catch(console.error);
   },
 
   /**
    * Copy the XPath of the selected Node to the clipboard.
    */
   copyXPath: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyxpath");
+    this.telemetry.logScalar("devtools.copy.xpath.opened", 1);
     this.selection.nodeFront.getXPath().then(path => {
       clipboardHelper.copyString(path);
     }).catch(console.error);
   },
 
   /**
    * Initiate gcli screenshot command on selected node.
    */
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -67,16 +67,17 @@ support-files =
   lib_react_dom_16.2.0_min.js
   lib_react_with_addons_15.3.1_min.js
   lib_react_with_addons_15.4.1.js
   react_external_listeners.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_markup_accessibility_focus_blur.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_navigation.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_new_selection.js]
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -39,16 +39,17 @@ support-files =
   doc_urls_clickable.html
   doc_variables_1.html
   doc_variables_2.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_rules_add-property-and-reselect.js]
 [browser_rules_add-property-cancel_01.js]
 [browser_rules_add-property-cancel_02.js]
 [browser_rules_add-property-cancel_03.js]
 [browser_rules_add-property-commented.js]
--- a/devtools/client/inspector/shared/test/browser.ini
+++ b/devtools/client/inspector/shared/test/browser.ini
@@ -12,16 +12,17 @@ support-files =
   doc_content_stylesheet_script.css
   doc_content_stylesheet_xul.css
   doc_frame_script.js
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_styleinspector_context-menu-copy-urls.js]
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -40,16 +40,17 @@ support-files =
   doc_inspector_select-last-selected-01.html
   doc_inspector_select-last-selected-02.html
   doc_inspector_svg.svg
   head.js
   img_browser_inspector_highlighter-eyedropper-image.png
   shared-head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_inspector_addNode_01.js]
 [browser_inspector_addNode_02.js]
 [browser_inspector_addNode_03.js]
 [browser_inspector_addSidebarTab.js]
 [browser_inspector_breadcrumbs.js]
@@ -141,19 +142,16 @@ skip-if = (e10s && debug) # Bug 1250058 
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
-[browser_inspector_menu-02-copy-items.js]
-subsuite = clipboard
-skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items-svg.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
deleted file mode 100644
--- a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// Test that the various copy items in the context menu works correctly.
-
-const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
-const SELECTOR_UNIQUE = "devtools.copy.unique.css.selector.opened";
-const SELECTOR_FULL = "devtools.copy.full.css.selector.opened";
-const XPATH = "devtools.copy.xpath.opened";
-
-const COPY_ITEMS_TEST_DATA = [
-  {
-    desc: "copy inner html",
-    id: "node-menu-copyinner",
-    selector: "[data-id=\"copy\"]",
-    text: "Paragraph for testing copy",
-  },
-  {
-    desc: "copy outer html",
-    id: "node-menu-copyouter",
-    selector: "[data-id=\"copy\"]",
-    text: "<p data-id=\"copy\">Paragraph for testing copy</p>",
-  },
-  {
-    desc: "copy unique selector",
-    id: "node-menu-copyuniqueselector",
-    selector: "[data-id=\"copy\"]",
-    text: "body > div:nth-child(1) > p:nth-child(2)",
-  },
-  {
-    desc: "copy CSS path",
-    id: "node-menu-copycsspath",
-    selector: "[data-id=\"copy\"]",
-    text: "html body div p",
-  },
-  {
-    desc: "copy XPath",
-    id: "node-menu-copyxpath",
-    selector: "[data-id=\"copy\"]",
-    text: "/html/body/div/p[1]",
-  },
-  {
-    desc: "copy image data uri",
-    id: "node-menu-copyimagedatauri",
-    selector: "#copyimage",
-    text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
-      "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
-  },
-];
-
-add_task(async function() {
-  let Telemetry = loadTelemetryAndRecordLogs();
-  let { inspector } = await openInspectorForURL(TEST_URL);
-
-  for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) {
-    info("Testing " + desc);
-    await selectNode(selector, inspector);
-
-    let allMenuItems = openContextMenuAndGetAllItems(inspector);
-    let item = allMenuItems.find(i => i.id === id);
-    ok(item, "The popup has a " + desc + " menu item.");
-
-    await waitForClipboardPromise(() => item.click(), text);
-  }
-
-  checkTelemetryResults(Telemetry);
-  stopRecordingTelemetryLogs(Telemetry);
-});
-
-function checkTelemetryResults(Telemetry) {
-  let data = Telemetry.prototype.telemetryInfo;
-  let results = new Map();
-
-  for (let key in data) {
-    if (key.toLowerCase() === key) {
-      let pings = data[key].length;
-
-      results.set(key, pings);
-    }
-  }
-
-  is(results.size, 3, "The correct number of scalars were logged");
-
-  let pings = checkPings(SELECTOR_UNIQUE, results);
-  is(pings, 1, `${SELECTOR_UNIQUE} has just 1 ping`);
-
-  pings = checkPings(SELECTOR_FULL, results);
-  is(pings, 1, `${SELECTOR_FULL} has just 1 ping`);
-
-  pings = checkPings(XPATH, results);
-  is(pings, 1, `${XPATH} has just 1 ping`);
-}
-
-function checkPings(scalarId, results) {
-  return results.get(scalarId);
-}
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   simple_json.json
   simple_json.json^headers^
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_json_refresh.js]
 [browser_jsonview_bug_1380828.js]
 [browser_jsonview_chunked_json.js]
 support-files =
   chunked_json.sjs
 [browser_jsonview_content_type.js]
 [browser_jsonview_copy_headers.js]
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -3,16 +3,17 @@ tags = devtools devtools-memory
 subsuite = devtools
 support-files =
   head.js
   doc_big_tree.html
   doc_empty.html
   doc_steady_allocation.html
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_memory_allocationStackDisplay_01.js]
 skip-if = debug # bug 1219554
 [browser_memory_displays_01.js]
 [browser_memory_clear_snapshots.js]
 [browser_memory_diff_01.js]
 [browser_memory_dominator_trees_01.js]
 skip-if = ccov # bug 1347244
--- a/devtools/client/netmonitor/package.json
+++ b/devtools/client/netmonitor/package.json
@@ -7,18 +7,18 @@
   "description": "Network monitor in developer tools",
   "dependencies": {
     "babel-plugin-transform-flow-strip-types": "^6.22.0",
     "babel-plugin-transform-react-jsx": "^6.24.1",
     "babel-plugin-transform-object-rest-spread": "^6.26.0",
     "codemirror": "^5.24.2",
     "devtools-config": "=0.0.12",
     "devtools-contextmenu": "=0.0.3",
-    "devtools-launchpad": "^0.0.117",
-    "devtools-modules": "=0.0.32",
+    "devtools-launchpad": "^0.0.119",
+    "devtools-modules": "=0.0.35",
     "devtools-source-editor": "=0.0.3",
     "file-loader": "^1.1.6",
     "jszip": "^3.1.3",
     "react": "=16.2.0",
     "react-dom": "=16.2.0",
     "react-prop-types": "=0.4.0",
     "react-redux": "=5.0.6",
     "redux": "^3.7.2",
--- a/devtools/client/netmonitor/src/har/test/browser.ini
+++ b/devtools/client/netmonitor/src/har/test/browser.ini
@@ -6,14 +6,15 @@ support-files =
   head.js
   html_har_import-test-page.html
   html_har_post-data-test-page.html
   sjs_cache-test-server.sjs
   sjs_cookies-test-server.sjs
   !/devtools/client/netmonitor/test/head.js
   !/devtools/client/netmonitor/test/html_simple-test-page.html
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_net_har_copy_all_as_har.js]
 [browser_net_har_import.js]
 [browser_net_har_post_data.js]
 [browser_net_har_throttle_upload.js]
 [browser_net_har_post_data_on_get.js]
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -55,16 +55,17 @@ support-files =
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   xhr_bundle.js
   xhr_bundle.js.map
   xhr_original.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
 [browser_net_api-calls.js]
 [browser_net_background_update.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
 [browser_net_cause.js]
--- a/devtools/client/netmonitor/test/browser_net_headers_filter.js
+++ b/devtools/client/netmonitor/test/browser_net_headers_filter.js
@@ -7,35 +7,29 @@
  * Tests if Request-Headers and Response-Headers are correctly filtered in Headers tab.
  */
 add_task(async function() {
   let { tab, monitor } = await initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
-  let {
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   await wait;
 
   wait = waitUntil(() => document.querySelector(".headers-overview"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   await wait;
 
-  await waitUntil(() => {
-    let request = getSortedRequests(store.getState()).get(0);
-    return request.requestHeaders && request.responseHeaders;
-  });
+  await waitForRequestData(store, ["requestHeaders", "responseHeaders"]);
 
   document.querySelectorAll(".devtools-filterinput")[1].focus();
   EventUtils.synthesizeKey("con", {});
   await waitUntil(() => document.querySelector(".treeRow.hidden"));
 
   info("Check if Headers are filtered correctly");
 
   let totalResponseHeaders = ["cache-control", "connection", "content-length",
--- a/devtools/client/netmonitor/test/browser_net_headers_sorted.js
+++ b/devtools/client/netmonitor/test/browser_net_headers_sorted.js
@@ -27,32 +27,26 @@ add_task(async function() {
   await verifyHeaders(monitor);
   await verifyRawHeaders(monitor);
 
   // Clean up
   await teardown(monitor);
 });
 
 async function verifyHeaders(monitor) {
-  let { document, store, windowRequire } = monitor.panelWin;
-  let {
-    getSortedRequests,
-  } = windowRequire("devtools/client/netmonitor/src/selectors/index");
+  let { document, store } = monitor.panelWin;
 
   info("Check if Request-Headers and Response-Headers are sorted");
 
   let wait = waitForDOM(document, ".headers-overview");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   await wait;
 
-  await waitUntil(() => {
-    let request = getSortedRequests(store.getState()).get(0);
-    return request.requestHeaders && request.responseHeaders;
-  });
+  await waitForRequestData(store, ["requestHeaders", "responseHeaders"]);
 
   let expectedResponseHeaders = ["cache-control", "connection", "content-length",
                                  "content-type", "date", "expires", "foo-bar",
                                  "foo-bar", "foo-bar", "pragma", "server", "set-cookie",
                                  "set-cookie"];
   let expectedRequestHeaders = ["Accept", "Accept-Encoding", "Accept-Language",
                                 "Cache-Control", "Connection", "Cookie", "Host",
                                 "Pragma", "Upgrade-Insecure-Requests", "User-Agent"];
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -23,28 +23,27 @@ add_task(async function() {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   // Execute requests.
   await performRequests(monitor, tab, 2);
 
-  let origItem = getSortedRequests(store.getState()).get(0);
+  let origItemId = getSortedRequests(store.getState()).get(0).id;
 
-  store.dispatch(Actions.selectRequest(origItem.id));
+  store.dispatch(Actions.selectRequest(origItemId));
+  await waitForRequestData(store, ["requestHeaders", "requestPostData"], origItemId);
+
+  let origItem = getSortedRequests(store.getState()).get(0);
 
   // add a new custom request cloned from selected request
 
   store.dispatch(Actions.cloneSelectedRequest());
-  // FIXME: This used to be a generator function that nothing awaited on
-  // and therefore didn't run. It has been broken for some time.
-  if (false) {
-    testCustomForm(origItem);
-  }
+  await testCustomForm(origItem);
 
   let customItem = getSelectedRequest(store.getState());
   testCustomItem(customItem, origItem);
 
   // edit the custom request
   await editCustomForm();
 
   // FIXME: reread the customItem, it's been replaced by a new object (immutable!)
--- a/devtools/client/netmonitor/test/browser_net_resend_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -43,21 +43,19 @@ add_task(async function() {
 
   ok(item.requestHeadersAvailable, "headers are available for lazily fetching");
 
   if (item.requestHeadersAvailable && !item.requestHeaders) {
     requestData(item.id, "requestHeaders");
   }
 
   // Wait until requestHeaders packet gets updated.
-  await waitUntil(() => {
-    item = getSortedRequests(store.getState()).get(0);
-    return item.requestHeaders;
-  });
+  await waitForRequestData(store, ["requestHeaders"]);
 
+  item = getSortedRequests(store.getState()).get(0);
   is(item.method, "POST", "The request has the right method");
   is(item.url, requestUrl, "The request has the right URL");
 
   for (let { name, value } of item.requestHeaders.headers) {
     info(`Request header: ${name}: ${value}`);
   }
 
   function hasRequestHeader(name, value) {
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -99,20 +99,17 @@ function test() {
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
     expectEvent(EVENTS.RECEIVED_REQUEST_HEADERS, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.requestHeaders;
-      });
+      await waitForRequestData(store, ["requestHeaders"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.requestHeaders,
         "There should be a requestHeaders data available.");
       is(requestItem.requestHeaders.headers.length, 10,
         "The requestHeaders data has an incorrect |headers| property.");
       isnot(requestItem.requestHeaders.headersSize, 0,
@@ -125,20 +122,17 @@ function test() {
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
     expectEvent(EVENTS.RECEIVED_REQUEST_COOKIES, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.requestCookies;
-      });
+      await waitForRequestData(store, ["requestCookies"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.requestCookies,
         "There should be a requestCookies data available.");
       is(requestItem.requestCookies.length, 2,
         "The requestCookies data has an incorrect |cookies| property.");
 
@@ -151,20 +145,17 @@ function test() {
       );
     });
 
     monitor.panelWin.api.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
       ok(false, "Trap listener: this request doesn't have any post data.");
     });
 
     expectEvent(EVENTS.RECEIVED_RESPONSE_HEADERS, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.responseHeaders;
-      });
+      await waitForRequestData(store, ["responseHeaders"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.responseHeaders,
         "There should be a responseHeaders data available.");
       is(requestItem.responseHeaders.headers.length, 13,
         "The responseHeaders data has an incorrect |headers| property.");
       is(requestItem.responseHeaders.headersSize, 335,
@@ -175,20 +166,17 @@ function test() {
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
     expectEvent(EVENTS.RECEIVED_RESPONSE_COOKIES, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.responseCookies;
-      });
+      await waitForRequestData(store, ["responseCookies"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.responseCookies,
         "There should be a responseCookies data available.");
       is(requestItem.responseCookies.length, 2,
         "The responseCookies data has an incorrect |cookies| property.");
 
@@ -197,24 +185,22 @@ function test() {
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
     expectEvent(EVENTS.STARTED_RECEIVING_RESPONSE, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem &&
-               requestItem.httpVersion &&
-               requestItem.status &&
-               requestItem.statusText &&
-               requestItem.headersSize;
-      });
+      await waitForRequestData(store, [
+        "httpVersion",
+        "status",
+        "statusText",
+        "headersSize"
+      ]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(requestItem.httpVersion, "HTTP/1.1",
         "The httpVersion data has an incorrect value.");
       is(requestItem.status, "200",
         "The status data has an incorrect value.");
       is(requestItem.statusText, "Och Aye",
@@ -237,23 +223,21 @@ function test() {
         {
           status: "200",
           statusText: "Och Aye"
         }
       );
     });
 
     expectEvent(EVENTS.PAYLOAD_READY, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem &&
-               requestItem.transferredSize &&
-               requestItem.contentSize &&
-               requestItem.mimeType;
-      });
+      await waitForRequestData(store, [
+        "transferredSize",
+        "contentSize",
+        "mimeType"
+      ]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(requestItem.transferredSize, "347",
         "The transferredSize data has an incorrect value.");
       is(requestItem.contentSize, "12",
         "The contentSize data has an incorrect value.");
       is(requestItem.mimeType, "text/plain; charset=utf-8",
@@ -270,20 +254,17 @@ function test() {
           fullMimeType: "text/plain; charset=utf-8",
           transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 347),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
         }
       );
     });
 
     expectEvent(EVENTS.UPDATING_EVENT_TIMINGS, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.eventTimings;
-      });
+      await waitForRequestData(store, ["eventTimings"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       is(typeof requestItem.totalTime, "number",
         "The attached totalTime is incorrect.");
       ok(requestItem.totalTime >= 0,
         "The attached totalTime should be positive.");
 
@@ -295,20 +276,17 @@ function test() {
         SIMPLE_SJS,
         {
           time: true
         }
       );
     });
 
     expectEvent(EVENTS.RECEIVED_EVENT_TIMINGS, async () => {
-      await waitUntil(() => {
-        let requestItem = getSortedRequests(store.getState()).get(0);
-        return requestItem && requestItem.eventTimings;
-      });
+      await waitForRequestData(store, ["eventTimings"]);
 
       let requestItem = getSortedRequests(store.getState()).get(0);
 
       ok(requestItem.eventTimings,
         "There should be a eventTimings data available.");
       is(typeof requestItem.eventTimings.timings.blocked, "number",
         "The eventTimings data has an incorrect |timings.blocked| property.");
       is(typeof requestItem.eventTimings.timings.dns, "number",
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -44,20 +44,17 @@ async function throttleTest(actuallyThro
       resolve(response);
     });
   });
 
   let wait = waitForNetworkEvents(monitor, 1);
   await triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
   await wait;
 
-  await waitUntil(() => {
-    let requestItem = getSortedRequests(store.getState()).get(0);
-    return requestItem && requestItem.eventTimings;
-  });
+  await waitForRequestData(store, ["eventTimings"]);
 
   let requestItem = getSortedRequests(store.getState()).get(0);
   const reportedOneSecond = requestItem.eventTimings.timings.receive > 1000;
   if (actuallyThrottle) {
     ok(reportedOneSecond, "download reported as taking more than one second");
   } else {
     ok(!reportedOneSecond, "download reported as taking less than one second");
   }
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -1,28 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../shared/test/shared-head.js */
 /* exported Toolbox, restartNetMonitor, teardown, waitForExplicitFinish,
    verifyRequestItemTarget, waitFor, testFilterButtons,
    performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor,
-   testColumnsAlignment, hideColumn, showColumn, performRequests */
+   testColumnsAlignment, hideColumn, showColumn, performRequests, waitForRequestData */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
   this);
 
 const {
   getFormattedIPAndPort,
   getFormattedTime,
 } = require("devtools/client/netmonitor/src/utils/format-utils");
+
+const {
+  getSortedRequests,
+  getRequestById
+} = require("devtools/client/netmonitor/src/selectors/index");
+
 const {
   getUnicodeUrl,
   getUnicodeHostname,
 } = require("devtools/client/shared/unicode-url");
 const {
   getFormattedProtocol,
   getUrlBaseName,
   getUrlHost,
@@ -759,8 +765,35 @@ async function selectIndexAndWaitForSour
  */
 async function performRequests(monitor, tab, count) {
   let wait = waitForNetworkEvents(monitor, count);
   await ContentTask.spawn(tab.linkedBrowser, count, requestCount => {
     content.wrappedJSObject.performRequests(requestCount);
   });
   await wait;
 }
+
+/**
+ * Wait for lazy fields to be loaded in a request.
+ *
+ * @param Object Store redux store containing request list.
+ * @param array fields array of strings which contain field names to be checked
+ * on the request.
+ */
+function waitForRequestData(store, fields, id) {
+  return waitUntil(() => {
+    let item;
+    if (id) {
+      item = getRequestById(store.getState(), id);
+    } else {
+      item = getSortedRequests(store.getState()).get(0);
+    }
+    if (!item) {
+      return false;
+    }
+    for (const field of fields) {
+      if (!item[field]) {
+        return false;
+      }
+    }
+    return true;
+  });
+}
--- a/devtools/client/netmonitor/webpack.config.js
+++ b/devtools/client/netmonitor/webpack.config.js
@@ -83,17 +83,17 @@ let webpackConfig = {
       "devtools/client/shared/vendor/jszip": "jszip",
 
       "devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
 
       "devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
       "devtools/shared/fronts/timeline": path.join(__dirname, "../../client/shared/webpack/shims/fronts-timeline-shim"),
       "devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
       "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab": path.join(__dirname, "src/utils/open-request-in-tab"),
-      "devtools/client/shared/unicode-url": path.join(__dirname, "../../client/shared/webpack/shims/unicode-url-stub"),
+      "devtools/client/shared/unicode-url": "./node_modules/devtools-modules/src/unicode-url",
 
       // Locales need to be explicitly mapped to the en-US subfolder
       "devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
       "devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
       "devtools/startup/locales": path.join(__dirname, "../../shared/locales/en-US"),
       "toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
 
       // Unless a path explicitly needs to be rewritten or shimmed, all devtools paths can
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   doc_allocs.html
   doc_innerHTML.html
   doc_markers.html
   doc_simple-test.html
   doc_worker.html
   js_simpleWorker.js
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_aaa-run-first-leaktest.js]
 [browser_perf-button-states.js]
 [browser_perf-calltree-js-categories.js]
 [browser_perf-calltree-js-columns.js]
 [browser_perf-calltree-js-events.js]
 [browser_perf-calltree-memory-columns.js]
 [browser_perf-console-record-01.js]
--- a/devtools/client/performance/test/browser_perf-telemetry-01.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-01.js
@@ -1,53 +1,52 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 /**
  * Tests that the performance telemetry module records events at appropriate times.
  * Specificaly the state about a recording process.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { UI_ENABLE_MEMORY_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { PerformanceController } = panel.panelWin;
-
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let DURATION = "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS";
-  let COUNT = "DEVTOOLS_PERFTOOLS_RECORDING_COUNT";
-  let CONSOLE_COUNT = "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT";
-  let FEATURES = "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED";
-
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, false);
 
   await startRecording(panel);
   await stopRecording(panel);
 
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
 
   await startRecording(panel);
   await stopRecording(panel);
 
-  is(logs[DURATION].length, 2, `There are two entries for ${DURATION}.`);
-  ok(logs[DURATION].every(d => typeof d === "number"),
-     `Every ${DURATION} entry is a number.`);
-  is(logs[COUNT].length, 2, `There are two entries for ${COUNT}.`);
-  is(logs[CONSOLE_COUNT], void 0, `There are no entries for ${CONSOLE_COUNT}.`);
-  is(logs[FEATURES].length, 8,
-     `There are two recordings worth of entries for ${FEATURES}.`);
-  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === true),
-     "One feature entry for memory enabled.");
-  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === false),
-    "One feature entry for memory disabled.");
+  checkResults();
 
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMarkers", [0, 2, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMemory", [1, 1, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withAllocations", [2, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withTicks", [0, 2, 0], "array");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-02.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-02.js
@@ -8,41 +8,42 @@
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { EVENTS, PerformanceController } = panel.panelWin;
 
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let EXPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG";
-  let IMPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG";
-
   await startRecording(panel);
   await stopRecording(panel);
 
   let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
 
   let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
   await PerformanceController.exportRecording(PerformanceController.getCurrentRecording(),
     file);
   await exported;
 
-  ok(logs[EXPORTED], `A telemetry entry for ${EXPORTED} exists after exporting.`);
-
   let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
   await PerformanceController.importRecording(file);
   await imported;
 
-  ok(logs[IMPORTED], `A telemetry entry for ${IMPORTED} exists after importing.`);
-
+  checkResults();
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG", "", [0, 1, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG", "", [0, 1, 0], "array");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-03.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-03.js
@@ -8,49 +8,50 @@
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let {
     EVENTS,
-    PerformanceController,
     DetailsView,
     JsCallTreeView,
     JsFlameGraphView
   } = panel.panelWin;
 
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let VIEWS = "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS";
-
   await startRecording(panel);
   await stopRecording(panel);
 
   let calltreeRendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
   let flamegraphRendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
 
   // Go through some views to check later.
   await DetailsView.selectView("js-calltree");
   await calltreeRendered;
 
   await DetailsView.selectView("js-flamegraph");
   await flamegraphRendered;
 
   await teardownToolboxAndRemoveTab(panel);
 
-  // Check views after destruction to ensure `js-flamegraph` gets called
-  // with a time during destruction.
-  ok(logs[VIEWS].find(r => r[0] === "waterfall" && typeof r[1] === "number"),
-     `${VIEWS} for waterfall view and time.`);
-  ok(logs[VIEWS].find(r => r[0] === "js-calltree" && typeof r[1] === "number"),
-     `${VIEWS} for js-calltree view and time.`);
-  ok(logs[VIEWS].find(r => r[0] === "js-flamegraph" && typeof r[1] === "number"),
-     `${VIEWS} for js-flamegraph view and time.`);
+  checkResults();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "js-calltree", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "js-flamegraph", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "waterfall", null, "hasentries");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-04.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-04.js
@@ -6,45 +6,49 @@
  * Tests that the performance telemetry module records events at appropriate times.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 
 add_task(async function() {
+  startTelemetry();
+
   let { target, console } = await initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = await initPerformanceInTab({ tab: target.tab });
-  let { PerformanceController } = panel.panelWin;
-
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let DURATION = "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS";
-  let CONSOLE_COUNT = "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT";
-  let FEATURES = "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED";
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   await console.profile("rust");
   await started;
 
   let stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   await console.profileEnd("rust");
   await stopped;
 
-  is(logs[DURATION].length, 1, `There is one entry for ${DURATION}.`);
-  ok(logs[DURATION].every(d => typeof d === "number"),
-     `Every ${DURATION} entry is a number.`);
-  is(logs[CONSOLE_COUNT].length, 1, `There is one entry for ${CONSOLE_COUNT}.`);
-  is(logs[FEATURES].length, 4,
-     `There is one recording worth of entries for ${FEATURES}.`);
-
+  checkResults();
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMarkers", [0, 1, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMemory", [1, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withAllocations", [1, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withTicks", [0, 1, 0], "array");
+}
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -1,14 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../../shared/test/telemetry-test-helpers.js */
+
 "use strict";
 
 const { require, loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 /* exported loader, either, click, dblclick, mousedown, rightMousedown, key */
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 // Performance tests are much heavier because of their reliance on the
 // profiler module, memory measurements, frequent canvas operations etc. Many of
 // of them take longer than 30 seconds to finish on try server VMs, even though
 // they superficially do very little.
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
   doc_page_state.html
   geolocation.html
   head.js
   touch.html
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_cmd_click.js]
 [browser_device_change.js]
 [browser_device_custom_remove.js]
 [browser_device_custom.js]
 [browser_device_modal_error.js]
--- a/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
+++ b/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
-const URL =
-  "data:text/html;charset=utf8,browser_telemetry_activate_rdm.js";
+const URL = "data:text/html;charset=utf8,browser_telemetry_activate_rdm.js";
 const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
 const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "activate",
     object: "responsive_design",
     value: null,
--- a/devtools/client/shadereditor/test/browser.ini
+++ b/devtools/client/shadereditor/test/browser.ini
@@ -5,16 +5,17 @@ support-files =
   doc_blended-geometry.html
   doc_multiple-contexts.html
   doc_overlapping-geometry.html
   doc_shader-order.html
   doc_simple-canvas.html
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_se_aaa_run_first_leaktest.js]
 [browser_se_bfcache.js]
 skip-if = true # Bug 942473, caused by Bug 940541
 [browser_se_editors-contents.js]
 [browser_se_editors-error-gutter.js]
 [browser_se_editors-error-tooltip.js]
 [browser_se_editors-lazy-init.js]
--- a/devtools/client/shared/components/test/browser/browser.ini
+++ b/devtools/client/shared/components/test/browser/browser.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_notification_box_basic.js]
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -145,25 +145,16 @@ class Telemetry {
         histogram: "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT",
       },
       pickereyedropper: {
         histogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT",
       },
       toolbareyedropper: {
         scalar: "devtools.toolbar.eyedropper.opened",
       },
-      copyuniquecssselector: {
-        scalar: "devtools.copy.unique.css.selector.opened",
-      },
-      copyfullcssselector: {
-        scalar: "devtools.copy.full.css.selector.opened",
-      },
-      copyxpath: {
-        scalar: "devtools.copy.xpath.opened",
-      },
       developertoolbar: {
         histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_COUNT",
         timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
       },
       aboutdebugging: {
         histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
         timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
       },
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -30,16 +30,17 @@ support-files =
   frame-script-utils.js
   head.js
   helper_color_data.js
   helper_html_tooltip.js
   helper_inplace_editor.js
   leakhunt.js
   shared-head.js
   shared-redux-head.js
+  telemetry-test-helpers.js
   test-actor-registry.js
   test-actor.js
   !/devtools/client/responsive.html/test/browser/devices.json
 
 [browser_css_angle.js]
 [browser_css_color.js]
 [browser_cubic-bezier-01.js]
 [browser_cubic-bezier-02.js]
@@ -169,20 +170,20 @@ skip-if = e10s # Test intermittently fai
 [browser_require_raw.js]
 [browser_spectrum.js]
 [browser_theme.js]
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
 [browser_telemetry_button_eyedropper.js]
 [browser_telemetry_button_paintflashing.js]
-skip-if = e10s # Bug 937167 - e10s paintflashing
 [browser_telemetry_button_responsive.js]
 skip-if = !e10s || os == "win" # RDM only works for remote tabs, Win: bug 1404197
 [browser_telemetry_button_scratchpad.js]
+[browser_telemetry_misc.js]
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_canvasdebugger.js]
 [browser_telemetry_toolboxtabs_inspector.js]
 [browser_telemetry_toolboxtabs_jsdebugger.js]
 [browser_telemetry_toolboxtabs_jsprofiler.js]
 [browser_telemetry_toolboxtabs_netmonitor.js]
 [browser_telemetry_toolboxtabs_options.js]
--- a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
+++ b/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
@@ -1,54 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8," +
   "<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
-const EYEDROPPER_OPENED = "devtools.toolbar.eyedropper.opened";
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
-  info("inspector opened");
 
   info("testing the eyedropper button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 async function testButton(toolbox, Telemetry) {
   info("Calling the eyedropper button's callback");
   // We call the button callback directly because we don't need to test the UI here, we're
   // only concerned about testing the telemetry probe.
   await toolbox.getPanel("inspector").showEyeDropper();
 
-  checkTelemetryResults(Telemetry);
+  checkResults();
 }
 
-function checkTelemetryResults(Telemetry) {
-  let data = Telemetry.prototype.telemetryInfo;
-  let results = new Map();
-
-  for (let key in data) {
-    if (key.toLowerCase() === key) {
-      let pings = data[key].length;
-
-      results.set(key, pings);
-    }
-  }
-
-  is(results.size, 1, "The correct number of scalars were logged");
-
-  let pings = checkPings(EYEDROPPER_OPENED, results);
-  is(pings, 1, `${EYEDROPPER_OPENED} has just 1 ping`);
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("devtools.")
+  // here.
+  checkTelemetry("devtools.toolbar.eyedropper.opened", "", 1, "scalar");
 }
-
-function checkPings(scalarId, results) {
-  return results.get(scalarId);
-}
--- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
+++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
@@ -8,88 +8,63 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_button_paintflashing.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
+
   await pushPref("devtools.command-button-paintflashing.enabled", true);
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   info("testing the paintflashing button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-paintflashing");
 
   let button = toolbox.doc.querySelector("#command-button-paintflashing");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(toolbox, button, 4);
-  checkResults("_PAINTFLASHING_", Telemetry);
+  checkResults();
 }
 
 async function delayedClicks(toolbox, node, clicks) {
   for (let i = 0; i < clicks; i++) {
     await new Promise(resolve => {
       // See TOOL_DELAY for why we need setTimeout here
       setTimeout(() => resolve(), TOOL_DELAY);
     });
 
     let { CommandState } = require("devtools/shared/gcli/command-state");
     let clicked = new Promise(resolve => {
-      CommandState.on("changed", function changed(type, { command }) {
+      CommandState.on("changed", function changed({command}) {
         if (command === "paintflashing") {
           CommandState.off("changed", changed);
           resolve();
         }
       });
     });
 
     info("Clicking button " + node.id);
     node.click();
 
     await clicked;
   }
 }
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PAINTFLASHING_")
+  // here.
+  checkTelemetry("DEVTOOLS_PAINTFLASHING_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_button_responsive.js
+++ b/devtools/client/shared/test/browser_telemetry_button_responsive.js
@@ -29,39 +29,38 @@ registerCleanupFunction(() => {
   asyncStorage.removeItem("devtools.devices.url_cache");
   asyncStorage.removeItem("devtools.devices.local");
 });
 
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   info("testing the responsivedesign button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-responsive");
 
   let button = toolbox.doc.querySelector("#command-button-responsive");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(button, 4);
 
-  checkResults("_RESPONSIVE_", Telemetry);
+  checkResults();
 }
 
 function waitForToggle() {
   return new Promise(resolve => {
     let handler = () => {
       ResponsiveUIManager.off("on", handler);
       ResponsiveUIManager.off("off", handler);
       resolve();
@@ -77,39 +76,14 @@ var delayedClicks = async function(node,
     let toggled = waitForToggle();
     node.click();
     await toggled;
     // See TOOL_DELAY for why we need setTimeout here
     await DevToolsUtils.waitForTime(TOOL_DELAY);
   }
 };
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_RESPONSIVE_")
+  // here.
+  checkTelemetry("DEVTOOLS_RESPONSIVE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_button_scratchpad.js
+++ b/devtools/client/shared/test/browser_telemetry_button_scratchpad.js
@@ -8,33 +8,32 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_button_scratchpad.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await pushPref("devtools.command-button-scratchpad.enabled", true);
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   let onAllWindowsOpened = trackScratchpadWindows();
 
   info("testing the scratchpad button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
   await onAllWindowsOpened;
 
-  checkResults("_SCRATCHPAD_", Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 function trackScratchpadWindows() {
   info("register the window observer to track when scratchpad windows open");
 
   let numScratchpads = 0;
@@ -63,17 +62,17 @@ function trackScratchpadWindows() {
             });
           }
         }, {once: true});
       }
     });
   });
 }
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-scratchpad");
   let button = toolbox.doc.querySelector("#command-button-scratchpad");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(button, 4);
 }
 
 function delayedClicks(node, clicks) {
@@ -90,39 +89,13 @@ function delayedClicks(node, clicks) {
         resolve(node);
       } else {
         setTimeout(delayedClick, TOOL_DELAY);
       }
     }, TOOL_DELAY);
   });
 }
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_SCRATCHPAD_")
+  // here.
+  checkTelemetry("DEVTOOLS_SCRATCHPAD_WINDOW_OPENED_COUNT", "", [4, 0, 0], "array");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_telemetry_misc.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_misc.js</p>";
+const TOOL_DELAY = 0;
+
+add_task(async function() {
+  await addTab(TEST_URI);
+
+  startTelemetry();
+
+  await openAndCloseToolbox(1, TOOL_DELAY, "inspector");
+  checkResults();
+
+  gBrowser.removeCurrentTab();
+});
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_TOOLBOX_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -7,39 +7,38 @@
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_sidebar.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   await testSidebar(toolbox);
-  checkResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 function testSidebar(toolbox) {
   info("Testing sidebar");
 
   let inspector = toolbox.getCurrentPanel();
   let sidebarTools = ["ruleview", "computedview", "layoutview", "fontinspector",
                       "animationinspector"];
 
   // Concatenate the array with itself so that we can open each tool twice.
-  sidebarTools.push.apply(sidebarTools, sidebarTools);
+  sidebarTools = [...sidebarTools, ...sidebarTools];
 
   return new Promise(resolve => {
     // See TOOL_DELAY for why we need setTimeout here
     setTimeout(function selectSidebarTab() {
       let tool = sidebarTools.pop();
       if (tool) {
         inspector.sidebar.select(tool);
         setTimeout(function() {
@@ -47,39 +46,21 @@ function testSidebar(toolbox) {
         }, TOOL_DELAY);
       } else {
         resolve();
       }
     }, TOOL_DELAY);
   });
 }
 
-function checkResults(Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_")) {
-      // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we
-      // skip them here because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId === "DEVTOOLS_TOOLBOX_OPENED_COUNT") {
-      is(value.length, 1, histId + " has only one entry");
-    } else if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", [3, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_toolbox.js
+++ b/devtools/client/shared/test/browser_telemetry_toolbox.js
@@ -7,16 +7,23 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolbox.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(3, TOOL_DELAY, "inspector");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_TOOLBOX_")
+  // here.
+  checkTelemetry("DEVTOOLS_TOOLBOX_OPENED_COUNT", "", [3, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
@@ -11,19 +11,25 @@ const TEST_URI = "data:text/html;charset
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   info("Activate the canvasdebugger");
   let originalPref = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "canvasdebugger");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activate the canvasdebugger");
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_CANVASDEBUGGER")
+  // here.
+  checkTelemetry("DEVTOOLS_CANVASDEBUGGER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_inspector.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_inspector.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_inspector.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "inspector");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_INSPECTOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_jsdebugger.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "jsdebugger");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_JSDEBUGGER_")
+  // here.
+  checkTelemetry("DEVTOOLS_JSDEBUGGER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_JSDEBUGGER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_jsprofiler.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "performance");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_JSPROFILER")
+  // here.
+  checkTelemetry("DEVTOOLS_JSPROFILER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
@@ -7,17 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_netmonitor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "netmonitor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
 
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_NETMONITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_NETMONITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_options.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "options");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_OPTIONS_")
+  // here.
+  checkTelemetry("DEVTOOLS_OPTIONS_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js
@@ -12,19 +12,25 @@ const TOOL_DELAY = 200;
 const TOOL_PREF = "devtools.shadereditor.enabled";
 
 add_task(async function() {
   info("Active the sharer editor");
   let originalPref = Services.prefs.getBoolPref(TOOL_PREF);
   Services.prefs.setBoolPref(TOOL_PREF, true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "shadereditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activate the sharer editor");
   Services.prefs.setBoolPref(TOOL_PREF, originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_SHADEREDITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_SHADEREDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_storage.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 1000;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "storage");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_STORAGE_")
+  // here.
+  checkTelemetry("DEVTOOLS_STORAGE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
@@ -7,17 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_styleeditor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "styleeditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
 
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_STYLEEDITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_STYLEEDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js
@@ -11,19 +11,25 @@ const TEST_URI = "data:text/html;charset
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   info("Activating the webaudioeditor");
   let originalPref = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "webaudioeditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activating the webaudioeditor");
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_WEBAUDIOEDITOR")
+  // here.
+  checkTelemetry("DEVTOOLS_WEBAUDIOEDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_styleeditor_webconsole.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "webconsole");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_WEBCONSOLE_")
+  // here.
+  checkTelemetry("DEVTOOLS_WEBCONSOLE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_WEBCONSOLE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/head.js
+++ b/devtools/client/shared/test/head.js
@@ -1,13 +1,14 @@
 /* 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/. */
 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
 /* import-globals-from shared-head.js */
+/* import-globals-from telemetry-test-helpers.js */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
 const {DOMHelpers} = ChromeUtils.import("resource://devtools/client/shared/DOMHelpers.jsm", {});
 const {Hosts} = require("devtools/client/framework/toolbox-hosts");
@@ -114,46 +115,16 @@ async function(type = "bottom", src = CH
     iframe.setAttribute("src", src);
     domHelper.onceDOMReady(resolve);
   });
 
   return [host, iframe.contentWindow, iframe.contentDocument];
 };
 
 /**
- * Check the correctness of the data recorded in Telemetry after
- * loadTelemetryAndRecordLogs was called.
- */
-function checkTelemetryResults(Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let histId in result) {
-    let value = result[histId];
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
-}
-
-/**
  * Open and close the toolbox in the current browser tab, several times, waiting
  * some amount of time in between.
  * @param {Number} nbOfTimes
  * @param {Number} usageTime in milliseconds
  * @param {String} toolId
  */
 async function openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
   for (let i = 0; i < nbOfTimes; i++) {
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -32,16 +32,19 @@ const KeyShortcuts = require("devtools/c
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
 const URL_ROOT = CHROME_URL_ROOT.replace("chrome://mochitests/content/",
                                          "http://example.com/");
 const URL_ROOT_SSL = CHROME_URL_ROOT.replace("chrome://mochitests/content/",
                                              "https://example.com/");
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 // Force devtools to be initialized so menu items and keyboard shortcuts get installed
 require("devtools/client/framework/devtools-browser");
 
 // All test are asynchronous
 waitForExplicitFinish();
 
 var EXPECTED_DTU_ASSERT_FAILURE_COUNT = 0;
 
@@ -577,67 +580,16 @@ function lookupPath(obj, path) {
 }
 
 var closeToolbox = async function() {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   await gDevTools.closeToolbox(target);
 };
 
 /**
- * Load the Telemetry utils, then stub Telemetry.prototype.log and
- * Telemetry.prototype.logKeyed in order to record everything that's logged in
- * it.
- * Store all recordings in Telemetry.telemetryInfo.
- * @return {Telemetry}
- */
-function loadTelemetryAndRecordLogs() {
-  info("Mock the Telemetry log function to record logged information");
-
-  let Telemetry = require("devtools/client/shared/telemetry");
-  Telemetry.prototype.telemetryInfo = {};
-  Telemetry.prototype._oldlog = Telemetry.prototype.log;
-  Telemetry.prototype.log = function(histogramId, value) {
-    if (!this.telemetryInfo) {
-      // Telemetry instance still in use after stopRecordingTelemetryLogs
-      return;
-    }
-    if (histogramId) {
-      if (!this.telemetryInfo[histogramId]) {
-        this.telemetryInfo[histogramId] = [];
-      }
-      this.telemetryInfo[histogramId].push(value);
-    }
-  };
-  Telemetry.prototype._oldlogScalar = Telemetry.prototype.logScalar;
-  Telemetry.prototype.logScalar = Telemetry.prototype.log;
-  Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
-  Telemetry.prototype.logKeyed = function(histogramId, key, value) {
-    this.log(`${histogramId}|${key}`, value);
-  };
-
-  return Telemetry;
-}
-
-/**
- * Stop recording the Telemetry logs and put back the utils as it was before.
- * @param {Telemetry} Required Telemetry
- *        Telemetry object that needs to be stopped.
- */
-function stopRecordingTelemetryLogs(Telemetry) {
-  info("Stopping Telemetry");
-  Telemetry.prototype.log = Telemetry.prototype._oldlog;
-  Telemetry.prototype.logScalar = Telemetry.prototype._oldlogScalar;
-  Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
-  delete Telemetry.prototype._oldlog;
-  delete Telemetry.prototype._oldlogScalar;
-  delete Telemetry.prototype._oldlogKeyed;
-  delete Telemetry.prototype.telemetryInfo;
-}
-
-/**
  * Clean the logical clipboard content. This method only clears the OS clipboard on
  * Windows (see Bug 666254).
  */
 function emptyClipboard() {
   let clipboard = Services.clipboard;
   clipboard.emptyClipboard(clipboard.kGlobalClipboard);
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/telemetry-test-helpers.js
@@ -0,0 +1,294 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global is ok registerCleanupFunction Services */
+
+"use strict";
+
+// We try to avoid polluting the global scope as far as possible by defining
+// constants in the methods that use them because this script is not sandboxed
+// meaning that it is loaded via Services.scriptloader.loadSubScript()
+
+class TelemetryHelpers {
+  constructor() {
+    this.oldCanRecord = Services.telemetry.canRecordExtended;
+    this.generateTelemetryTests = this.generateTelemetryTests.bind(this);
+    registerCleanupFunction(this.stopTelemetry.bind(this));
+  }
+
+  /**
+   * Allow collection of extended telemetry data.
+   */
+  startTelemetry() {
+    Services.telemetry.canRecordExtended = true;
+  }
+
+  /**
+   * Clear all telemetry types.
+   */
+  stopTelemetry() {
+    this.clearToolsOpenedPref();
+    Services.telemetry.canRecordExtended = this.oldCanRecord;
+
+    // Clear histograms, scalars and Telemetry Events.
+    this.clearHistograms(Services.telemetry.snapshotHistograms);
+    this.clearHistograms(Services.telemetry.snapshotKeyedHistograms);
+    Services.telemetry.clearScalars();
+    Services.telemetry.clearEvents();
+  }
+
+  /**
+   * Clears both OPTIN and OPTOUT versions of Telemetry Histograms.
+   *
+   * @param {Function} snapshotFunc
+   *        The function used to take the snapshot. This can be one of the
+   *        following:
+   *          - Services.telemetry.snapshotHistograms
+   *          - Services.telemetry.snapshotKeyedHistograms
+   *
+   *        `snapshotFunc(OPTIN, true, true)` should clear the histograms but this
+   *        only deletes seemingly random histograms, hence this method.
+   */
+  clearHistograms(snapshotFunc) {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+    const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
+    const tel = Services.telemetry;
+
+    for (let optInOut of [OPTIN, OPTOUT]) {
+      const snapshot = snapshotFunc(optInOut, true, false).parent;
+      const histKeys = Object.keys(snapshot);
+
+      for (let getHistogram of [tel.getHistogramById, tel.getKeyedHistogramById]) {
+        for (let key of histKeys) {
+          try {
+            getHistogram(key).clear();
+          } catch (e) {
+            // Some histograms may have already been cleaned up by the system so we
+            // swallow the "histogram does not exist" error silently here.
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Clears the pref that is used to log telemetry data once per browser version.
+   */
+  clearToolsOpenedPref() {
+    const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
+
+    Services.prefs.clearUserPref(TOOLS_OPENED_PREF);
+  }
+
+  /**
+   * Check the value of a given telemetry histogram.
+   *
+   * @param  {String} histId
+   *         Histogram id
+   * @param  {String} key
+   *         Keyed histogram key
+   * @param  {Array|Number} expected
+   *         Expected value
+   * @param  {String} checkType
+   *         "array" (default) - Check that an array matches the histogram data.
+   *         "hasentries"  - For non-enumerated linear and exponential
+   *                             histograms. This checks for at least one entry.
+   *         "scalar" - Telemetry type is a scalar.
+   *         "keyedscalar" - Telemetry type is a keyed scalar.
+   */
+  checkTelemetry(histId, key, expected, checkType) {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+
+    let actual;
+    let msg;
+
+    if (checkType === "array" || checkType === "hasentries") {
+      if (key) {
+        const keyedHistogram =
+          Services.telemetry.getKeyedHistogramById(histId).snapshot();
+        const result = keyedHistogram[key];
+
+        if (result) {
+          actual = result.counts;
+        } else {
+          ok(false, `${histId}[${key}] exists`);
+          return;
+        }
+      } else {
+        actual = Services.telemetry.getHistogramById(histId).snapshot().counts;
+      }
+    }
+
+    switch (checkType) {
+      case "array":
+        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
+        is(JSON.stringify(actual), JSON.stringify(expected), msg);
+        break;
+      case "hasentries":
+        let hasEntry = actual.some(num => num > 0);
+        if (key) {
+          ok(hasEntry, `${histId}["${key}"] has at least one entry.`);
+        } else {
+          ok(hasEntry, `${histId} has at least one entry.`);
+        }
+        break;
+      case "scalar":
+        const scalars =
+          Services.telemetry.snapshotScalars(OPTIN, false).parent;
+
+        is(scalars[histId], expected, `${histId} correct`);
+        break;
+      case "keyedscalar":
+        const keyedScalars =
+          Services.telemetry.snapshotKeyedScalars(OPTIN, false).parent;
+        const value = keyedScalars[histId][key];
+
+        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
+        is(value, expected, msg);
+        break;
+    }
+  }
+
+  /**
+   * Generate telemetry tests. You should call generateTelemetryTests("DEVTOOLS_")
+   * from your result checking code in telemetry tests. It logs checkTelemetry
+   * calls for all changed telemetry values.
+   *
+   * @param  {String} prefix
+   *         Optionally limits results to histogram ids starting with prefix.
+   */
+  generateTelemetryTests(prefix = "") {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+
+    // Get all histograms and scalars
+    const histograms =
+      Services.telemetry.snapshotHistograms(OPTIN, true, false).parent;
+    const keyedHistograms =
+      Services.telemetry.snapshotKeyedHistograms(OPTIN, true, false).parent;
+    const scalars =
+      Services.telemetry.snapshotScalars(OPTIN, false).parent;
+    const keyedScalars =
+      Services.telemetry.snapshotKeyedScalars(OPTIN, false).parent;
+    const allHistograms = Object.assign({},
+                                        histograms,
+                                        keyedHistograms,
+                                        scalars,
+                                        keyedScalars);
+    // Get all keys
+    const histIds = Object.keys(allHistograms)
+                          .filter(histId => histId.startsWith(prefix));
+
+    dump("=".repeat(80) + "\n");
+    for (let histId of histIds) {
+      let snapshot = allHistograms[histId];
+
+      if (histId === histId.toLowerCase()) {
+        if (typeof snapshot === "object") {
+          // Keyed Scalar
+          const keys = Object.keys(snapshot);
+
+          for (let key of keys) {
+            const value = snapshot[key];
+
+            dump(`checkTelemetry("${histId}", "${key}", ${value}, "keyedscalar");\n`);
+          }
+        } else {
+          // Scalar
+          dump(`checkTelemetry("${histId}", "", ${snapshot}, "scalar");\n`);
+        }
+      } else if (typeof snapshot.histogram_type !== "undefined" &&
+                typeof snapshot.counts !== "undefined") {
+        // Histogram
+        const actual = snapshot.counts;
+
+        this.displayDataFromHistogramSnapshot(snapshot, "", histId, actual);
+      } else {
+        // Keyed Histogram
+        const keys = Object.keys(snapshot);
+
+        for (let key of keys) {
+          const value = snapshot[key];
+          const actual = value.counts;
+
+          this.displayDataFromHistogramSnapshot(value, key, histId, actual);
+        }
+      }
+    }
+    dump("=".repeat(80) + "\n");
+  }
+
+  /**
+   * Generates the inner contents of a test's checkTelemetry() method.
+   *
+   * @param {HistogramSnapshot} snapshot
+   *        A snapshot of a telemetry chart obtained via snapshotHistograms or
+   *        similar.
+   * @param {String} key
+   *        Only used for keyed histograms. This is the key we are interested in
+   *        checking.
+   * @param {String} histId
+   *        The histogram ID.
+   * @param {Array|String|Boolean} actual
+   *        The value of the histogram data.
+   */
+  displayDataFromHistogramSnapshot(snapshot, key, histId, actual) {
+    key = key ? `"${key}"` : `""`;
+
+    switch (snapshot.histogram_type) {
+      case Services.telemetry.HISTOGRAM_EXPONENTIAL:
+      case Services.telemetry.HISTOGRAM_LINEAR:
+        let total = 0;
+        for (let val of actual) {
+          total += val;
+        }
+
+        if (histId.endsWith("_ENUMERATED")) {
+          if (total > 0) {
+            actual = actual.toSource();
+            dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+          }
+          return;
+        }
+
+        dump(`checkTelemetry("${histId}", ${key}, null, "hasentries");\n`);
+        break;
+      case Services.telemetry.HISTOGRAM_BOOLEAN:
+        actual = actual.toSource();
+
+        if (actual !== "[0, 0, 0]") {
+          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        }
+        break;
+      case Services.telemetry.HISTOGRAM_FLAG:
+        actual = actual.toSource();
+
+        if (actual !== "[1, 0, 0]") {
+          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        }
+        break;
+      case Services.telemetry.HISTOGRAM_COUNT:
+        actual = actual.toSource();
+
+        dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        break;
+    }
+  }
+}
+
+// "exports"... because this is a helper and not imported via require we need to
+// expose the three main methods that should be used by tests. The reason this
+// is not imported via require is because it needs access to test methods
+// (is, ok etc).
+
+/* eslint-disable no-unused-vars */
+const telemetryHelpers = new TelemetryHelpers();
+const generateTelemetryTests = telemetryHelpers.generateTelemetryTests;
+const checkTelemetry = telemetryHelpers.checkTelemetry;
+const startTelemetry = telemetryHelpers.startTelemetry;
+/* eslint-enable no-unused-vars */
--- a/devtools/client/shared/unicode-url.js
+++ b/devtools/client/shared/unicode-url.js
@@ -1,21 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-// This file is a chrome-API-dependent version of the module
-// devtools/client/shared/webpack/shims/unicode-url-stub.js, so that it can
-// take advantage of utilizing chrome APIs. But because of this, it isn't
-// intended to be used in Chrome-API-free applications, such as the Launchpad.
+// This file is a chrome-API-dependent version of the file
+// packages/devtools-modules/src/unicode-url.js at
+// https://github.com/devtools-html/devtools-core, so that this
+// chrome-API-dependent version can take advantage of utilizing chrome APIs. But
+// because of this, it isn't intended to be used in Chrome-API-free
+// applications, such as the Launchpad.
 //
 // Please keep in mind that if the feature in this file has changed, don't
-// forget to also change that accordingly in
-// devtools/client/shared/webpack/shims/unicode-url-stub.js.
+// forget to also change that accordingly in the file
+// packages/devtools-modules/src/unicode-url.js at
+// https://github.com/devtools-html/devtools-core
 
 const { Cc, Ci } = require("chrome");
 const idnService =
         Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
 
 /**
  * Gets a readble Unicode hostname from a hostname.
  *
deleted file mode 100644
--- a/devtools/client/shared/webpack/shims/unicode-url-stub.js
+++ /dev/null
@@ -1,32 +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/. */
-
-// TODO This file aims to implement a Chrome-API-free replacement for
-// devtools/client/shared/unicode-url.js, so that it can be used in the
-// Launchpad.
-//
-// Currently this is just a dummpy mock of
-// devtools/client/shared/unicode-url.js, no actual functionaly involved.
-// Eventually we'll want to implement it. Once implemented, we should keep the
-// feature the same as devtools/client/shared/unicode-url.js.
-
-"use strict";
-
-function getUnicodeHostname(hostname) {
-  return hostname;
-}
-
-function getUnicodeUrlPath(urlPath) {
-  return urlPath;
-}
-
-function getUnicodeUrl(url) {
-  return url;
-}
-
-module.exports = {
-  getUnicodeHostname,
-  getUnicodeUrlPath,
-  getUnicodeUrl,
-};
--- a/devtools/client/sourceeditor/test/browser.ini
+++ b/devtools/client/sourceeditor/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   css_statemachine_tests.json
   css_autocompletion_tests.json
   head.js
   head.xul
   helper_codemirror_runner.js
   cm_mode_ruby.js
   cm_script_injection_test.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_editor_autocomplete_basic.js]
 [browser_editor_autocomplete_events.js]
 [browser_editor_autocomplete_js.js]
 [browser_editor_basic.js]
 [browser_editor_cursor.js]
 [browser_editor_find_again.js]
 [browser_editor_goto_line.js]
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   storage-secured-iframe.html
   storage-secured-iframe-usercontextid.html
   storage-sessionstorage.html
   storage-unsecured-iframe.html
   storage-unsecured-iframe-usercontextid.html
   storage-updates.html
   head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_storage_basic.js]
 [browser_storage_basic_usercontextid_1.js]
 [browser_storage_basic_usercontextid_2.js]
 tags = usercontextid
 [browser_storage_basic_with_fragment.js]
 [browser_storage_cache_delete.js]
 [browser_storage_cache_error.js]
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -62,16 +62,17 @@ support-files =
   sync.html
   utf-16.css
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/shared/test/head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/responsive.html/test/browser/devices.json
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_styleeditor_add_stylesheet.js]
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_autocomplete-disabled.js]
 [browser_styleeditor_bom.js]
 [browser_styleeditor_bug_740541_iframes.js]
--- a/devtools/client/webaudioeditor/test/browser.ini
+++ b/devtools/client/webaudioeditor/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
   doc_bug_1112378.html
   doc_bug_1125817.html
   doc_bug_1130901.html
   doc_bug_1141261.html
   440hz_sine.ogg
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-type.js]
 [browser_audionode-actor-source.js]
 [browser_audionode-actor-bypass.js]
--- a/devtools/client/webconsole/package.json
+++ b/devtools/client/webconsole/package.json
@@ -9,18 +9,18 @@
     "dev": " cross-env NODE_ENV=development node bin/dev-server"
   },
   "dependencies": {
     "babel-plugin-transform-flow-strip-types": "^6.22.0",
     "babel-plugin-transform-react-jsx": "^6.24.1",
     "babel-plugin-transform-object-rest-spread": "^6.26.0",
     "cross-env": "^3.1.3",
     "devtools-config": "0.0.12",
-    "devtools-launchpad": "^0.0.117",
-    "devtools-modules": "0.0.31",
+    "devtools-launchpad": "^0.0.119",
+    "devtools-modules": "0.0.35",
     "file-loader": "^1.1.6",
     "netmonitor": "file:../netmonitor",
     "raw-loader": "^0.5.1",
     "react": "=16.2.0",
     "react-dom": "=16.2.0",
     "react-prop-types": "=0.4.0",
     "react-redux": "=5.0.6",
     "redux": "^3.7.2",
--- a/devtools/client/webconsole/test/fixtures/stub-generators/browser.ini
+++ b/devtools/client/webconsole/test/fixtures/stub-generators/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   ../stubs/*
   test-console-api.html
   test-css-message.html
   test-network-event.html
 
 [browser_webconsole_check_stubs_console_api.js]
 [browser_webconsole_check_stubs_css_message.js]
 [browser_webconsole_check_stubs_evaluation_result.js]
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -151,16 +151,17 @@ support-files =
   test-trackingprotection-securityerrors.html
   test-webconsole-error-observer.html
   test-websocket.html
   test-websocket.js
   testscript.js
   !/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
   !/image/test/mochitest/blue.png
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_console.js]
 [browser_console_clear_cache.js]
 [browser_console_clear_method.js]
 skip-if = true # Bug 1437843
 [browser_console_consolejsm_output.js]
--- a/devtools/client/webconsole/webpack.config.js
+++ b/devtools/client/webconsole/webpack.config.js
@@ -87,21 +87,20 @@ webpackConfig.resolve = {
     "devtools/client/shared/vendor/reselect": "reselect",
 
     "resource://gre/modules/AppConstants.jsm": path.join(__dirname, "../../client/shared/webpack/shims/app-constants-stub"),
 
     "devtools/client/framework/devtools": path.join(__dirname, "../../client/shared/webpack/shims/framework-devtools-shim"),
     "devtools/client/framework/menu": "devtools-modules/src/menu",
     "devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
 
-    "devtools/client/shared/unicode-url": path.join(__dirname, "../../client/shared/webpack/shims/unicode-url-stub"),
+    "devtools/client/shared/unicode-url": "./node_modules/devtools-modules/src/unicode-url",
     "devtools/client/shared/zoom-keys": "devtools-modules/src/zoom-keys",
 
     "devtools/shared/fronts/timeline": path.join(__dirname, "../../client/shared/webpack/shims/fronts-timeline-shim"),
-    "devtools/shared/old-event-emitter": "devtools-modules/src/utils/event-emitter",
     "devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
     "devtools/shared/client/debugger-client": path.join(__dirname, "test/fixtures/DebuggerClient"),
     "devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
     "devtools/shared/platform/stack": path.join(__dirname, "../../client/shared/webpack/shims/platform-stack-stub"),
 
     // Locales need to be explicitly mapped to the en-US subfolder
     "toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
     "devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
--- a/devtools/docs/getting-started/development-profiles.md
+++ b/devtools/docs/getting-started/development-profiles.md
@@ -25,27 +25,27 @@ Next time you start Firefox with `./mach
 ## Enable additional logging
 
 You can change the value of these preferences by going to `about:config`:
 
 | Preference name | Value | Comments |
 | --------------- | --------------- | -------- |
 | `browser.dom.window.dump.enabled` | `true` | Adds global `dump` function to log strings to `stdout` |
 | `devtools.debugger.log` (*) | `true` | Dump packets sent over remote debugging protocol to `stdout`.<br /><br />The [remote protocol inspector add-on](https://github.com/firebug/rdp-inspector/wiki) might be useful too. |
-| `devtools.dump.emit` (*) | `true` | Log event notifications from the EventEmitter class<br />(found at `devtools/shared/old-event-emitter.js`). |
+| `devtools.dump.emit` (*) | `true` | Log event notifications from the EventEmitter class<br />(found at `devtools/shared/event-emitter.js`). |
 
 Preferences marked with a (`*`) also require `browser.dom.window.dump.enabled` in order to work. You might not want to enable *all* of those all the time, as they can cause the output to be way too verbose, but they might be useful if you're working on a server actor, for example<!--TODO link to actors doc-->.
 
 Restart the browser to apply configuration changes.
 
 ## Enable remote debugging and the Browser Toolbox
 
 These settings allow you to use the [browser toolbox](https://developer.mozilla.org/docs/Tools/Browser_Toolbox) to inspect the DevTools themselves, set breakpoints inside of DevTools code, and run the [Scratchpad](https://developer.mozilla.org/en-US/docs/Tools/Scratchpad) in the *Browser* environment.
 
-Open DevTools, and click the "Toolbox Options" gear icon in the top right (the image underneath is outdated). <!--TODO update image--> 
+Open DevTools, and click the "Toolbox Options" gear icon in the top right (the image underneath is outdated). <!--TODO update image-->
 
 Make sure the following two options are checked:
 
 - Enable browser chrome and add-on debugging toolboxes
 - Enable remote debugging
 
 ![Settings for developer tools - "Enable Chrome Debugging" and "Enable Remote Debugging"](../resources/DevToolsDeveloperSettings.png)
 
--- a/devtools/server/actors/highlighters/utils/canvas.js
+++ b/devtools/server/actors/highlighters/utils/canvas.js
@@ -281,18 +281,22 @@ function getBoundsFromPoints(points) {
  *           The current matrix.
  *         - {Boolean} hasNodeTransformations
  *           true if the node has transformed and false otherwise.
  */
 function getCurrentMatrix(element, window) {
   let computedStyle = getComputedStyle(element);
 
   let paddingTop = parseFloat(computedStyle.paddingTop);
+  let paddingRight = parseFloat(computedStyle.paddingRight);
+  let paddingBottom = parseFloat(computedStyle.paddingBottom);
   let paddingLeft = parseFloat(computedStyle.paddingLeft);
   let borderTop = parseFloat(computedStyle.borderTopWidth);
+  let borderRight = parseFloat(computedStyle.borderRightWidth);
+  let borderBottom = parseFloat(computedStyle.borderBottomWidth);
   let borderLeft = parseFloat(computedStyle.borderLeftWidth);
 
   let nodeMatrix = getNodeTransformationMatrix(element, window.document.documentElement);
 
   let currentMatrix = identity();
   let hasNodeTransformations = false;
 
   // Scale based on the device pixel ratio.
@@ -308,18 +312,18 @@ function getCurrentMatrix(element, windo
   }
 
   // Translate the origin based on the node's padding and border values.
   currentMatrix = multiply(currentMatrix,
     translate(paddingLeft + borderLeft, paddingTop + borderTop));
 
   // Adjust as needed to match the writing mode and text direction of the element.
   let size = {
-    width: element.offsetWidth,
-    height: element.offsetHeight,
+    width: element.offsetWidth - borderLeft - borderRight - paddingLeft - paddingRight,
+    height: element.offsetHeight - borderTop - borderBottom - paddingTop - paddingBottom,
   };
   let writingModeMatrix = getWritingModeMatrix(size, computedStyle);
   if (!isIdentity(writingModeMatrix)) {
     currentMatrix = multiply(currentMatrix, writingModeMatrix);
   }
 
   return { currentMatrix, hasNodeTransformations };
 }
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -21,16 +21,17 @@ support-files =
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
   storage-helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/server/tests/mochitest/hello-actor.js
 
 [browser_accessibility_node.js]
 [browser_accessibility_node_events.js]
 [browser_accessibility_simple.js]
 [browser_accessibility_walker.js]
 [browser_actor_error.js]
 [browser_animation_emitMutations.js]
--- a/devtools/shared/layout/dom-matrix-2d.js
+++ b/devtools/shared/layout/dom-matrix-2d.js
@@ -227,17 +227,18 @@ function getNodeTransformationMatrix(nod
 }
 exports.getNodeTransformationMatrix = getNodeTransformationMatrix;
 
 /**
  * Returns the matrix to rotate, translate, and reflect (if needed) from the element's
  * top-left origin into the actual writing mode and text direction applied to the element.
  *
  * @param  {Object} size
- *         An element's untransformed `width` and `height`.
+ *         An element's untransformed content `width` and `height` (excluding any margin,
+ *         borders, or padding).
  * @param  {Object} style
  *         The computed `writingMode` and `direction` properties for the element.
  * @return {Array}
  *         The matrix with adjustments for writing mode and text direction, if any.
  */
 function getWritingModeMatrix(size, style) {
   let currentMatrix = identity();
   let { width, height } = size;
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -59,17 +59,16 @@ DevToolsModules(
     'extend.js',
     'flags.js',
     'generate-uuid.js',
     'indentation.js',
     'indexed-db.js',
     'l10n.js',
     'loader-plugin-raw.jsm',
     'Loader.jsm',
-    'old-event-emitter.js',
     'Parser.jsm',
     'path.js',
     'plural-form.js',
     'protocol.js',
     'system.js',
     'task.js',
     'ThreadSafeDevToolsUtils.js',
     'throttle.js',
deleted file mode 100644
--- a/devtools/shared/old-event-emitter.js
+++ /dev/null
@@ -1,195 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const Services = require("Services");
-const defer = require("devtools/shared/defer");
-const { getNthPathExcluding } = require("devtools/shared/platform/stack");
-let loggingEnabled = false;
-
-if (!isWorker) {
-  loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
-  Services.prefs.addObserver("devtools.dump.emit", {
-    observe: () => {
-      loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
-    }
-  });
-}
-
-let EventEmitter = this.EventEmitter = function() {};
-module.exports = EventEmitter;
-
-/**
- * Decorate an object with event emitter functionality.
- *
- * @param Object objectToDecorate
- *        Bind all public methods of EventEmitter to
- *        the objectToDecorate object.
- * @return Object the object given.
- */
-EventEmitter.decorate = function(objectToDecorate) {
-  let emitter = new EventEmitter();
-  objectToDecorate.on = emitter.on.bind(emitter);
-  objectToDecorate.off = emitter.off.bind(emitter);
-  objectToDecorate.once = emitter.once.bind(emitter);
-  objectToDecorate.emit = emitter.emit.bind(emitter);
-
-  return objectToDecorate;
-};
-
-EventEmitter.prototype = {
-  /**
-   * Connect a listener.
-   *
-   * @param string event
-   *        The event name to which we're connecting.
-   * @param function listener
-   *        Called when the event is fired.
-   */
-  on(event, listener) {
-    if (!this._eventEmitterListeners) {
-      this._eventEmitterListeners = new Map();
-    }
-    if (!this._eventEmitterListeners.has(event)) {
-      this._eventEmitterListeners.set(event, []);
-    }
-    this._eventEmitterListeners.get(event).push(listener);
-  },
-
-  /**
-   * Listen for the next time an event is fired.
-   *
-   * @param string event
-   *        The event name to which we're connecting.
-   * @param function listener
-   *        (Optional) Called when the event is fired. Will be called at most
-   *        one time.
-   * @return promise
-   *        A promise which is resolved when the event next happens. The
-   *        resolution value of the promise is the first event argument. If
-   *        you need access to second or subsequent event arguments (it's rare
-   *        that this is needed) then use listener
-   */
-  once(event, listener) {
-    let deferred = defer();
-
-    let handler = (_, first, ...rest) => {
-      this.off(event, handler);
-      if (listener) {
-        listener(event, first, ...rest);
-      }
-      deferred.resolve(first);
-    };
-
-    handler._originalListener = listener;
-    this.on(event, handler);
-
-    return deferred.promise;
-  },
-
-  /**
-   * Remove a previously-registered event listener.  Works for events
-   * registered with either on or once.
-   *
-   * @param string event
-   *        The event name whose listener we're disconnecting.
-   * @param function listener
-   *        The listener to remove.
-   */
-  off(event, listener) {
-    if (!this._eventEmitterListeners) {
-      return;
-    }
-    let listeners = this._eventEmitterListeners.get(event);
-    if (listeners) {
-      this._eventEmitterListeners.set(event, listeners.filter(l => {
-        return l !== listener && l._originalListener !== listener;
-      }));
-    }
-  },
-
-  /**
-   * Emit an event.  All arguments to this method will
-   * be sent to listener functions.
-   */
-  emit(event) {
-    this.logEvent(event, arguments);
-
-    if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(event)) {
-      return;
-    }
-
-    let originalListeners = this._eventEmitterListeners.get(event);
-    for (let listener of this._eventEmitterListeners.get(event)) {
-      // If the object was destroyed during event emission, stop
-      // emitting.
-      if (!this._eventEmitterListeners) {
-        break;
-      }
-
-      // If listeners were removed during emission, make sure the
-      // event handler we're going to fire wasn't removed.
-      if (originalListeners === this._eventEmitterListeners.get(event) ||
-        this._eventEmitterListeners.get(event).some(l => l === listener)) {
-        try {
-          listener.apply(null, arguments);
-        } catch (ex) {
-          // Prevent a bad listener from interfering with the others.
-          let msg = ex + ": " + ex.stack;
-          console.error(msg);
-          dump(msg + "\n");
-        }
-      }
-    }
-  },
-
-  logEvent(event, args) {
-    if (!loggingEnabled) {
-      return;
-    }
-
-    let description = getNthPathExcluding(0, "devtools/shared/old-event-emitter.js");
-
-    let argOut = "(";
-    if (args.length === 1) {
-      argOut += event;
-    }
-
-    let out = "EMITTING: ";
-
-    // We need this try / catch to prevent any dead object errors.
-    try {
-      for (let i = 1; i < args.length; i++) {
-        if (i === 1) {
-          argOut = "(" + event + ", ";
-        } else {
-          argOut += ", ";
-        }
-
-        let arg = args[i];
-        argOut += arg;
-
-        if (arg && arg.nodeName) {
-          argOut += " (" + arg.nodeName;
-          if (arg.id) {
-            argOut += "#" + arg.id;
-          }
-          if (arg.className) {
-            argOut += "." + arg.className;
-          }
-          argOut += ")";
-        }
-      }
-    } catch (e) {
-      // Object is dead so the toolbox is most likely shutting down,
-      // do nothing.
-    }
-
-    argOut += ")";
-    out += "emit" + argOut + " from " + description + "\n";
-
-    dump(out);
-  },
-};
--- a/devtools/shared/tests/browser/browser.ini
+++ b/devtools/shared/tests/browser/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   ../../../server/tests/browser/head.js
 
 [browser_async_storage.js]
 [browser_l10n_localizeMarkup.js]
deleted file mode 100644
--- a/devtools/shared/tests/mochitest/test_eventemitter_basic.html
+++ /dev/null
@@ -1,192 +0,0 @@
-<!DOCTYPE html>
-<!--
-  Any copyright is dedicated to the Public Domain.
-  http://creativecommons.org/publicdomain/zero/1.0/
--->
-
-<html>
-
-  <head>
-    <meta charset="utf8">
-    <title></title>
-
-    <script type="application/javascript"
-            src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-    <link rel="stylesheet" type="text/css"
-          href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  </head>
-
-  <body>
-
-    <script type="application/javascript">
-      "use strict";
-
-      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-      const promise = require("promise");
-      const EventEmitter = require("devtools/shared/old-event-emitter");
-
-      SimpleTest.waitForExplicitFinish();
-
-      testEmitter();
-      testEmitter({});
-
-      testPromise()
-          .catch(ok.bind(null, false))
-          .then(SimpleTest.finish);
-
-      function testEmitter(aObject) {
-        let emitter;
-
-        if (aObject) {
-          emitter = aObject;
-          EventEmitter.decorate(emitter);
-        } else {
-          emitter = new EventEmitter();
-        }
-
-        ok(emitter, "We have an event emitter");
-
-        let beenHere1 = false;
-        let beenHere2 = false;
-
-        emitter.on("next", next);
-        emitter.emit("next", "abc", "def");
-
-        function next(eventName, str1, str2) {
-          is(eventName, "next", "Got event");
-          is(str1, "abc", "Argument 1 is correct");
-          is(str2, "def", "Argument 2 is correct");
-
-          ok(!beenHere1, "first time in next callback");
-          beenHere1 = true;
-
-          emitter.off("next", next);
-
-          emitter.emit("next");
-
-          emitter.once("onlyonce", onlyOnce);
-
-          emitter.emit("onlyonce");
-          emitter.emit("onlyonce");
-        }
-
-        function onlyOnce() {
-          ok(!beenHere2, "\"once\" listener has been called once");
-          beenHere2 = true;
-          emitter.emit("onlyonce");
-
-          testThrowingExceptionInListener();
-        }
-
-        function testThrowingExceptionInListener() {
-          function throwListener() {
-            emitter.off("throw-exception");
-            throw {
-              toString: () => "foo",
-              stack: "bar",
-            };
-          }
-
-          emitter.on("throw-exception", throwListener);
-          emitter.emit("throw-exception");
-
-          killItWhileEmitting();
-        }
-
-        function killItWhileEmitting() {
-          function c1() {
-            ok(true, "c1 called");
-          }
-          function c2() {
-            ok(true, "c2 called");
-            emitter.off("tick", c3);
-          }
-          function c3() {
-            ok(false, "c3 should not be called");
-          }
-          function c4() {
-            ok(true, "c4 called");
-          }
-
-          emitter.on("tick", c1);
-          emitter.on("tick", c2);
-          emitter.on("tick", c3);
-          emitter.on("tick", c4);
-
-          emitter.emit("tick");
-
-          offAfterOnce();
-        }
-
-        function offAfterOnce() {
-          let enteredC1 = false;
-
-          function c1() {
-            enteredC1 = true;
-          }
-
-          emitter.once("oao", c1);
-          emitter.off("oao", c1);
-
-          emitter.emit("oao");
-
-          ok(!enteredC1, "c1 should not be called");
-        }
-      }
-
-      function testPromise() {
-        let emitter = new EventEmitter();
-        let p = emitter.once("thing");
-
-        // Check that the promise is only resolved once event though we
-        // emit("thing") more than once
-        let firstCallbackCalled = false;
-        let check1 = p.then(arg => {
-          is(firstCallbackCalled, false, "first callback called only once");
-          firstCallbackCalled = true;
-          is(arg, "happened", "correct arg in promise");
-          return "rval from c1";
-        });
-
-        emitter.emit("thing", "happened", "ignored");
-
-        // Check that the promise is resolved asynchronously
-        let secondCallbackCalled = false;
-        let check2 = p.then(arg => {
-          ok(true, "second callback called");
-          is(arg, "happened", "correct arg in promise");
-          secondCallbackCalled = true;
-          is(arg, "happened", "correct arg in promise (a second time)");
-          return "rval from c2";
-        });
-
-        // Shouldn't call any of the above listeners
-        emitter.emit("thing", "trashinate");
-
-        // Check that we can still separate events with different names
-        // and that it works with no parameters
-        let pfoo = emitter.once("foo");
-        let pbar = emitter.once("bar");
-
-        let check3 = pfoo.then(arg => {
-          ok(arg === undefined, "no arg for foo event");
-          return "rval from c3";
-        });
-
-        pbar.then(() => {
-          ok(false, "pbar should not be called");
-        });
-
-        emitter.emit("foo");
-
-        is(secondCallbackCalled, false, "second callback not called yet");
-
-        return promise.all([ check1, check2, check3 ]).then(args => {
-          is(args[0], "rval from c1", "callback 1 done good");
-          is(args[1], "rval from c2", "callback 2 done good");
-          is(args[2], "rval from c3", "callback 3 done good");
-        });
-      }
-    </script>
-  </body>
-</html>
deleted file mode 100644
--- a/devtools/shared/tests/unit/test_old_eventemitter_basic.js
+++ /dev/null
@@ -1,208 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const {
-  ConsoleAPIListener
-} = require("devtools/server/actors/webconsole/listeners");
-const EventEmitter = require("devtools/shared/old-event-emitter");
-
-/**
- * Each method of this object is a test; tests can be synchronous or asynchronous:
- *
- * 1. Plain functions are synchronous tests.
- * 2. methods with `async` keyword are asynchronous tests.
- * 3. methods with `done` as argument are asynchronous tests (`done` needs to be called to
- *    finish the test).
- */
-const TESTS = {
-  testEventEmitterCreation() {
-    let emitter = getEventEmitter();
-
-    ok(emitter, "We have an event emitter");
-  },
-
-  testEmittingEvents(done) {
-    let emitter = getEventEmitter();
-
-    let beenHere1 = false;
-    let beenHere2 = false;
-
-    function next(eventName, str1, str2) {
-      equal(eventName, "next", "Got event");
-      equal(str1, "abc", "Argument 1 is correct");
-      equal(str2, "def", "Argument 2 is correct");
-
-      ok(!beenHere1, "first time in next callback");
-      beenHere1 = true;
-
-      emitter.off("next", next);
-
-      emitter.emit("next");
-
-      emitter.once("onlyonce", onlyOnce);
-
-      emitter.emit("onlyonce");
-      emitter.emit("onlyonce");
-    }
-
-    function onlyOnce() {
-      ok(!beenHere2, "\"once\" listener has been called once");
-      beenHere2 = true;
-      emitter.emit("onlyonce");
-
-      done();
-    }
-
-    emitter.on("next", next);
-    emitter.emit("next", "abc", "def");
-  },
-
-  testThrowingExceptionInListener(done) {
-    let emitter = getEventEmitter();
-    let listener = new ConsoleAPIListener(null, {
-      onConsoleAPICall(message) {
-        equal(message.level, "error");
-        equal(message.arguments[0], "foo: bar");
-        listener.destroy();
-        done();
-      }
-    });
-
-    listener.init();
-
-    function throwListener() {
-      emitter.off("throw-exception");
-      throw Object.create({
-        toString: () => "foo",
-        stack: "bar",
-      });
-    }
-
-    emitter.on("throw-exception", throwListener);
-    emitter.emit("throw-exception");
-  },
-
-  testKillItWhileEmitting(done) {
-    let emitter = getEventEmitter();
-
-    const c1 = () => ok(true, "c1 called");
-    const c2 = () => {
-      ok(true, "c2 called");
-      emitter.off("tick", c3);
-    };
-    const c3 = () => ok(false, "c3 should not be called");
-    const c4 = () => {
-      ok(true, "c4 called");
-      done();
-    };
-
-    emitter.on("tick", c1);
-    emitter.on("tick", c2);
-    emitter.on("tick", c3);
-    emitter.on("tick", c4);
-
-    emitter.emit("tick");
-  },
-
-  testOffAfterOnce() {
-    let emitter = getEventEmitter();
-
-    let enteredC1 = false;
-    let c1 = () => (enteredC1 = true);
-
-    emitter.once("oao", c1);
-    emitter.off("oao", c1);
-
-    emitter.emit("oao");
-
-    ok(!enteredC1, "c1 should not be called");
-  },
-
-  testPromise() {
-    let emitter = getEventEmitter();
-    let p = emitter.once("thing");
-
-    // Check that the promise is only resolved once event though we
-    // emit("thing") more than once
-    let firstCallbackCalled = false;
-    let check1 = p.then(arg => {
-      equal(firstCallbackCalled, false, "first callback called only once");
-      firstCallbackCalled = true;
-      equal(arg, "happened", "correct arg in promise");
-      return "rval from c1";
-    });
-
-    emitter.emit("thing", "happened", "ignored");
-
-    // Check that the promise is resolved asynchronously
-    let secondCallbackCalled = false;
-    let check2 = p.then(arg => {
-      ok(true, "second callback called");
-      equal(arg, "happened", "correct arg in promise");
-      secondCallbackCalled = true;
-      equal(arg, "happened", "correct arg in promise (a second time)");
-      return "rval from c2";
-    });
-
-    // Shouldn't call any of the above listeners
-    emitter.emit("thing", "trashinate");
-
-    // Check that we can still separate events with different names
-    // and that it works with no parameters
-    let pfoo = emitter.once("foo");
-    let pbar = emitter.once("bar");
-
-    let check3 = pfoo.then(arg => {
-      ok(arg === undefined, "no arg for foo event");
-      return "rval from c3";
-    });
-
-    pbar.then(() => {
-      ok(false, "pbar should not be called");
-    });
-
-    emitter.emit("foo");
-
-    equal(secondCallbackCalled, false, "second callback not called yet");
-
-    return Promise.all([ check1, check2, check3 ]).then(args => {
-      equal(args[0], "rval from c1", "callback 1 done good");
-      equal(args[1], "rval from c2", "callback 2 done good");
-      equal(args[2], "rval from c3", "callback 3 done good");
-    });
-  }
-};
-
-/**
- * Create a runnable tests based on the tests descriptor given.
- *
- * @param {Object} tests
- *  The tests descriptor object, contains the tests to run.
- */
-const runnable = (tests) => (async function() {
-  for (let name of Object.keys(tests)) {
-    info(name);
-    if (tests[name].length === 1) {
-      await (new Promise(resolve => tests[name](resolve)));
-    } else {
-      await tests[name]();
-    }
-  }
-});
-
-// We want to run the same tests for both an instance of `EventEmitter` and an object
-// decorate with EventEmitter; therefore we create two strategies (`createNewEmitter` and
-// `decorateObject`) and a factory (`getEventEmitter`), where the factory is the actual
-// function used in the tests.
-
-const createNewEmitter = () => new EventEmitter();
-const decorateObject = () => EventEmitter.decorate({});
-
-// First iteration of the tests with a new instance of `EventEmitter`.
-let getEventEmitter = createNewEmitter;
-add_task(runnable(TESTS));
-// Second iteration of the tests with an object decorate using `EventEmitter`
-add_task(() => (getEventEmitter = decorateObject));
-add_task(runnable(TESTS));
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -21,17 +21,16 @@ run-if = nightly_build
 [test_fetch-file.js]
 [test_fetch-http.js]
 [test_fetch-resource.js]
 [test_flatten.js]
 [test_indentation.js]
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_isSet.js]
-[test_old_eventemitter_basic.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_console_filtering.js]
 [test_pluralForm-english.js]
 [test_pluralForm-makeGetter.js]
 [test_prettifyCSS.js]
 [test_require_lazy.js]
 [test_require_raw.js]
--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -388,16 +388,19 @@ function getMatchedProps(obj, match) {
 function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
   let matches = new Set();
   let numProps = 0;
 
   // We need to go up the prototype chain.
   let iter = chainIterator(obj);
   for (obj of iter) {
     let props = getProperties(obj);
+    if (!props) {
+      continue;
+    }
     numProps += props.length;
 
     // If there are too many properties to event attempt autocompletion,
     // or if we have already added the max number, then stop looping
     // and return the partial set that has already been discovered.
     if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
         matches.size >= MAX_AUTOCOMPLETIONS) {
       break;
@@ -454,40 +457,60 @@ function getExactMatchImpl(obj, name, {c
   }
   return undefined;
 }
 
 var JSObjectSupport = {
   chainIterator: function* (obj) {
     while (obj) {
       yield obj;
-      obj = Object.getPrototypeOf(obj);
+      try {
+        obj = Object.getPrototypeOf(obj);
+      } catch (error) {
+        // The above can throw e.g. for some proxy objects.
+        return;
+      }
     }
   },
 
   getProperties: function(obj) {
-    return Object.getOwnPropertyNames(obj);
+    try {
+      return Object.getOwnPropertyNames(obj);
+    } catch (error) {
+      // The above can throw e.g. for some proxy objects.
+      return null;
+    }
   },
 
   getProperty: function() {
     // getProperty is unsafe with raw JS objects.
     throw new Error("Unimplemented!");
   },
 };
 
 var DebuggerObjectSupport = {
   chainIterator: function* (obj) {
     while (obj) {
       yield obj;
-      obj = obj.proto;
+      try {
+        obj = obj.proto;
+      } catch (error) {
+        // The above can throw e.g. for some proxy objects.
+        return;
+      }
     }
   },
 
   getProperties: function(obj) {
-    return obj.getOwnPropertyNames();
+    try {
+      return obj.getOwnPropertyNames();
+    } catch (error) {
+      // The above can throw e.g. for some proxy objects.
+      return null;
+    }
   },
 
   getProperty: function(obj, name, rootObj) {
     // This is left unimplemented in favor to DevToolsUtils.getProperty().
     throw new Error("Unimplemented!");
   },
 };
 
--- a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
+++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
@@ -18,34 +18,34 @@ let gState;
 let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");
 
 function evaluateJS(input, options = {}) {
   return new Promise((resolve, reject) => {
     gState.client.evaluateJSAsync(input, resolve, options);
   });
 }
 
-function autocompletePromise(str, cursor, frameActor) {
+function autocompletePromise(str, cursor = str.length, frameActor) {
   return new Promise(resolve => {
     gState.client.autocomplete(str, cursor, resolve, frameActor);
   });
 }
 
 // This test runs all of its assertions twice - once with
 // the tab as a target and once with a worker
 let runningInTab = true;
 function startTest({worker}) {
   if (worker) {
-    attachConsoleToWorker(["PageError"], onAttach);
+    attachConsoleToWorker(["PageError"], onAttach.bind(null, true));
   } else {
-    attachConsoleToTab(["PageError"], onAttach);
+    attachConsoleToTab(["PageError"], onAttach.bind(null, false));
   }
 };
 
-let onAttach = async function (aState, response) {
+let onAttach = async function (isWorker, aState, response) {
   gState = aState;
 
   let longStrLength = DebuggerServer.LONG_STRING_LENGTH;
 
   // Set up the global variables needed to test autocompletion
   // in the target.
   let script = `
     // This is for workers so autocomplete acts the same
@@ -66,42 +66,54 @@ let onAttach = async function (aState, r
     for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) {
       window.largeObject1['a' + i] = i;
     }
 
     window.largeObject2 = Object.create(null);
     for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) {
       window.largeObject2['a' + i] = i;
     }
+
+    window.proxy1 = new Proxy({foo: 1}, {
+      getPrototypeOf() { throw new Error() }
+    });
+    window.proxy2 = new Proxy(Object.create(Object.create(null, {foo:{}})), {
+      ownKeys() { throw new Error() }
+    });
   `;
 
   await evaluateJS(script);
 
   let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3,
                doAutocomplete4, doAutocompleteLarge1,
-               doAutocompleteLarge2];
+               doAutocompleteLarge2, doAutocompleteProxyThrowsPrototype,
+               doAutocompleteProxyThrowsOwnKeys];
+  if (!isWorker) {
+    // `Cu` is not defined in workers, then we can't test `Cu.Sandbox`
+    tests.push(doAutocompleteSandbox);
+  }
 
   runTests(tests, testEnd);
 };
 
 async function doAutocomplete1() {
   info("test autocomplete for 'window.foo'");
-  let response = await autocompletePromise("window.foo", 10);
+  let response = await autocompletePromise("window.foo");
   let matches = response.matches;
 
   is(response.matchProp, "foo", "matchProp");
   is(matches.length, 1, "matches.length");
   is(matches[0], "foobarObject", "matches[0]");
 
   nextTest();
 }
 
 async function doAutocomplete2() {
   info("test autocomplete for 'window.foobarObject.'");
-  let response = await autocompletePromise("window.foobarObject.", 20);
+  let response = await autocompletePromise("window.foobarObject.");
   let matches = response.matches;
 
   ok(!response.matchProp, "matchProp");
   is(matches.length, 7, "matches.length");
   checkObject(matches,
     ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
 
   nextTest();
@@ -119,46 +131,80 @@ async function doAutocomplete3() {
     ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
 
   nextTest();
 }
 
 async function doAutocomplete4() {
   // Check that completion requests can have no suggestions.
   info("test autocomplete for 'dump(window.foobarObject.)'");
-  let response = await autocompletePromise("dump(window.foobarObject.)", 26);
+  let response = await autocompletePromise("dump(window.foobarObject.)");
   ok(!response.matchProp, "matchProp");
   is(response.matches.length, 0, "matches.length");
 
   nextTest();
 }
 
 async function doAutocompleteLarge1() {
   // Check that completion requests with too large objects will
   // have no suggestions.
   info("test autocomplete for 'window.largeObject1.'");
-  let response = await autocompletePromise("window.largeObject1.", 20);
+  let response = await autocompletePromise("window.largeObject1.");
   ok(!response.matchProp, "matchProp");
   info (response.matches.join("|"));
   is(response.matches.length, 0, "Bailed out with too many properties");
 
   nextTest();
 }
 
 async function doAutocompleteLarge2() {
   // Check that completion requests with pretty large objects will
   // have MAX_AUTOCOMPLETIONS suggestions
   info("test autocomplete for 'window.largeObject2.'");
-  let response = await autocompletePromise("window.largeObject2.", 20);
+  let response = await autocompletePromise("window.largeObject2.");
   ok(!response.matchProp, "matchProp");
   is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
 
   nextTest();
 }
 
+async function doAutocompleteProxyThrowsPrototype() {
+  // Check that completion provides own properties even if [[GetPrototypeOf]] throws.
+  info("test autocomplete for 'window.proxy1.'");
+  let response = await autocompletePromise("window.proxy1.");
+  ok(!response.matchProp, "matchProp");
+  is(response.matches.length, 1, "matches.length");
+  checkObject(response.matches, ["foo"]);
+
+  nextTest();
+}
+
+async function doAutocompleteProxyThrowsOwnKeys() {
+  // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
+  info("test autocomplete for 'window.proxy2.'");
+  let response = await autocompletePromise("window.proxy2.");
+  ok(!response.matchProp, "matchProp");
+  is(response.matches.length, 1, "matches.length");
+  checkObject(response.matches, ["foo"]);
+
+  nextTest();
+}
+
+async function doAutocompleteSandbox() {
+  // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
+  info("test autocomplete for 'Cu.Sandbox.'");
+  let response = await autocompletePromise("Cu.Sandbox.");
+  ok(!response.matchProp, "matchProp");
+  let keys = Object.getOwnPropertyNames(Object.prototype).sort();
+  is(response.matches.length, keys.length, "matches.length");
+  checkObject(response.matches, keys);
+
+  nextTest();
+}
+
 function testEnd()
 {
   // If this is the first run, reload the page and do it again
   // in a worker.  Otherwise, end the test.
   closeDebugger(gState, function() {
     gState = null;
     if (runningInTab) {
       runningInTab = false;
--- a/dom/browser-element/mochitest/browserElement_Find.js
+++ b/dom/browser-element/mochitest/browserElement_Find.js
@@ -18,127 +18,131 @@ function runTest() {
     return new Promise((resolve) => {
       iframe.addEventListener(eventName, function(...args) {
         resolve(...args);
       }, {once: true});
     });
   }
 
   // Test if all key=>value pairs in o1 are present in o2.
-  const c = (o1, o2) => Object.keys(o1).every(k => o1[k] == o2[k]);
+  const c = (o1, o2, i) => {
+    for (let k of Object.keys(o1)) {
+      is(o1[k], o2[k], `Test ${i} should match for key ${k}`);
+    }
+  }
 
   let testCount = 0;
 
   once('mozbrowserloadend').then(() => {
     iframe.findAll('foo', 'case-insensitive');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'foo',
       searchLimit: 1000,
       activeMatchOrdinal: 1,
       numberOfMatches: 5,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('forward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'foo',
       searchLimit: 1000,
       activeMatchOrdinal: 2,
       numberOfMatches: 5,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('backward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'foo',
       searchLimit: 1000,
       activeMatchOrdinal: 1,
       numberOfMatches: 5,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findAll('xxx', 'case-sensitive');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'xxx',
       searchLimit: 1000,
       activeMatchOrdinal: 0,
       numberOfMatches: 0,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findAll('bar', 'case-insensitive');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'bar',
       searchLimit: 1000,
       activeMatchOrdinal: 1,
       numberOfMatches: 4,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('forward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'bar',
       searchLimit: 1000,
       activeMatchOrdinal: 2,
       numberOfMatches: 4,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('forward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'bar',
       searchLimit: 1000,
       activeMatchOrdinal: 3,
       numberOfMatches: 4,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('forward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'bar',
       searchLimit: 1000,
       activeMatchOrdinal: 4,
       numberOfMatches: 4,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.findNext('forward');
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: true,
       searchString: 'bar',
       searchLimit: 1000,
       activeMatchOrdinal: 1,
       numberOfMatches: 4,
-    }), `test ${testCount++}`);
+    }, testCount++);
     iframe.clearMatch();
     return once('mozbrowserfindchange');
   }).then(({detail}) => {
-    ok(c(detail, {
+    c(detail, {
       msg_name: "findchange",
       active: false
-    }), `test ${testCount++}`);
+    }, testCount++);
     SimpleTest.finish();
   });
 
   document.body.appendChild(iframe);
 
 }
 
 addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/chrome.ini
+++ b/dom/browser-element/mochitest/chrome.ini
@@ -31,16 +31,17 @@ support-files =
   file_illegal_web_manifest.html
 
 [test_browserElement_inproc_BackForward.html]
 [test_browserElement_inproc_BadScreenshot.html]
 [test_browserElement_inproc_DocumentFirstPaint.html]
 [test_browserElement_inproc_DOMRequestError.html]
 [test_browserElement_inproc_ExecuteScript.html]
 [test_browserElement_inproc_Find.html]
+disabled = Bug 1458393
 [test_browserElement_inproc_GetContentDimensions.html]
 [test_browserElement_inproc_GetScreenshot.html]
 [test_browserElement_inproc_GetScreenshotDppx.html]
 [test_browserElement_inproc_getWebManifest.html]
 [test_browserElement_inproc_NextPaint.html]
 [test_browserElement_inproc_PurgeHistory.html]
 [test_browserElement_inproc_ReloadPostRequest.html]
 disabled = no modal prompt on POST reload for chrome window
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -8,17 +8,16 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextComposition.h"
-#include "mozilla/TextEditor.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DragEvent.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/FrameLoaderBinding.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/TabChild.h"
@@ -902,28 +901,16 @@ EventStateManager::PreHandleEvent(nsPres
     NotifyTargetUserActivation(aEvent, aTargetContent);
     break;
   default:
     break;
   }
   return NS_OK;
 }
 
-static bool
-IsTextInput(nsIContent* aContent)
-{
-  MOZ_ASSERT(aContent);
-  if (!aContent->IsElement()) {
-    return false;
-  }
-  TextEditor* textEditor =
-    aContent->AsElement()->GetTextEditorInternal();
-  return textEditor && !textEditor->IsReadonly();
-}
-
 void
 EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
                                               nsIContent* aTargetContent)
 {
   if (!aEvent->IsTrusted()) {
     return;
   }
 
@@ -937,34 +924,16 @@ EventStateManager::NotifyTargetUserActiv
     return;
   }
 
   nsIDocument* doc = node->OwnerDoc();
   if (!doc || doc->HasBeenUserActivated()) {
     return;
   }
 
-  // Don't activate if the target content of the event is contentEditable or
-  // is inside an editable document, or is a text input control. Activating
-  // due to typing/clicking on a text input would be surprising user experience.
-  if (aTargetContent->IsEditable() ||
-      IsTextInput(aTargetContent)) {
-    return;
-  }
-
-  // Don't gesture activate for key events for keys which are likely
-  // to be interaction with the browser, OS, or likely to be scrolling.
-  WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
-  if (keyEvent && (!keyEvent->PseudoCharCode() ||
-                   (keyEvent->IsControl() && !keyEvent->IsAltGraph()) ||
-                   (keyEvent->IsAlt() && !keyEvent->IsAltGraph()) ||
-                   keyEvent->IsMeta() || keyEvent->IsOS())) {
-    return;
-  }
-
   MOZ_ASSERT(aEvent->mMessage == eKeyDown   ||
              aEvent->mMessage == eMouseDown ||
              aEvent->mMessage == ePointerDown ||
              aEvent->mMessage == eTouchEnd);
   doc->NotifyUserActivation();
 }
 
 already_AddRefed<EventStateManager>
deleted file mode 100644
--- a/dom/media/test/file_autoplay_policy_key_blacklist.html
+++ /dev/null
@@ -1,165 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-
-<head>
-  <title>Autoplay policy window</title>
-  <style>
-    video {
-      width: 50%;
-      height: 50%;
-    }
-    :focus {
-      background-color: blue;
-    }
-  </style>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="AutoplayTestUtils.js"></script>
-</head>
-
-<body>
-  <div id="x">This is a div with id=x.</div>
-  <pre id="test">
-      <input type="text" id="text-input"/>
-      <script>
-
-        window.ok = window.opener.ok;
-        window.is = window.opener.is;
-        window.info = window.opener.info;
-
-        // Keys that are expected to be not considered interaction with the page, and
-        // so not gesture activate the document.
-        let blacklistKeyPresses = [
-          "Tab",
-          "CapsLock",
-          "NumLock",
-          "ScrollLock",
-          "FnLock",
-          "Meta",
-          "OS",
-          "Hyper",
-          "Super",
-          "ContextMenu",
-          "ArrowUp",
-          "ArrowDown",
-          "ArrowLeft",
-          "ArrowRight",
-          "PageUp",
-          "PageDown",
-          "Home",
-          "End",
-          "Backspace",
-          "Fn",
-          "Alt",
-          "AltGraph",
-          "Control",
-          "Shift",
-          "Escape",
-        ];
-
-        let modifiedKeys = [
-          { key: "c", modifiers: { ctrlKey: true } },
-          { key: "V", modifiers: { ctrlKey: true, shiftKey: true } },
-          { key: "a", modifiers: { altKey: true } },
-          { key: "KEY_ArrowRight", modifiers: { metaKey: true } },
-          { key: "KEY_ArrowRight", modifiers: { altKey: true } },
-        ];
-
-        async function sendInput(element, name, input) {
-          synthesizeMouseAtCenter(input, {});
-          let played = await element.play().then(() => true, () => false);
-          ok(!played, "Clicking " + name + " should not activate document and should not unblock play");
-
-          synthesizeCompositionChange({
-            composition: {
-              string: "\u30E9\u30FC\u30E1\u30F3",
-              clauses: [
-                { length: 4, attr: COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            caret: { start: 4, length: 0 }
-          });
-          synthesizeComposition({ type: "compositioncommitasis" });
-          played = await element.play().then(() => true, () => false);
-          ok(!played, "Entering text to " + name + " via IME should not activate document and should not unblock play");
-
-          input.focus();
-          sendString("ascii text");
-          played = await element.play().then(() => true, () => false);
-          ok(!played, "Entering ASCII text into " + name + " should not activate document and should not unblock play");
-
-          input.blur();
-        }
-
-        async function testAutoplayKeyBlacklist(testCase, parent_window) {
-          let element = document.createElement("video");
-          element.preload = "auto";
-          element.src = "short.mp4";
-          document.body.appendChild(element);
-
-          await once(element, "loadedmetadata");
-
-          let played = await element.play().then(() => true, () => false);
-          is(played, false, "Document should start out not activated, with playback blocked.");
-
-          // Try pressing all the keys in the blacklist, then playing.
-          // Document should not be activated, so play should fail.
-
-          for (let key of blacklistKeyPresses) {
-            document.body.focus();
-            synthesizeKey("KEY_" + key);
-            played = await element.play().then(() => true, () => false);
-            is(played, false, "Key " + key + " should not activate document and should not unblock play");
-          }
-
-          // Try pressing some keys with modifiers.
-          let keyNames = (m) => Object.keys(m).join("+");
-          for (let x of modifiedKeys) {
-            document.body.focus();
-            synthesizeKey(x.key, x.modifiers);
-            played = await element.play().then(() => true, () => false);
-            is(played, false, "Key (" + x.key + "+" + keyNames(x.modifiers) + ") should not activate document and should not unblock play");
-          }
-
-          // Test that clicking/typing into an input element doesn't activate.
-          let input = document.getElementById("text-input");
-          await sendInput(element, "input", input);
-
-          // Test that clicking/typing into a contentEditable div element doesn't activate.
-          let div = document.getElementById("x");
-          div.contentEditable = "true";
-          await sendInput(element, "contentEditable div", div);
-          div.contentEditable = "false";
-
-          // Test that clicking/typing into a div inside a designMode document doesn't activate.
-          document.designMode = "on";
-          await sendInput(element, "div in designMode=on", div);
-          document.designMode = "off";
-
-          // Try pressing a key not in the blacklist, then playing.
-          // Document should be activated, and media should play.
-
-          is(document.activeElement, document.body, "Focus needs to be the document, not an editable or text control.");
-          synthesizeKey(" ");
-          played = await element.play().then(() => true, () => false);
-          is(played, true, "Space key should activate document and should unblock play");
-
-          removeNodeAndSource(element);
-        }
-
-        nextWindowMessage().then(
-          async (event) => {
-            try {
-              await testAutoplayKeyBlacklist(event.data, event.source);
-            } catch (e) {
-              ok(false, "Caught exception " + e + " " + e.message + " " + e.stackTrace);
-            }
-            event.source.postMessage("done", "*");
-          });
-
-      </script>
-    </pre>
-</body>
-
-</html>
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -433,17 +433,16 @@ support-files =
   detodos-short.opus
   detodos-short.opus^headers^
   dirac.ogg
   dirac.ogg^headers^
   dynamic_resource.sjs
   eme.js
   file_access_controls.html
   file_autoplay_policy_eventdown_activation.html
-  file_autoplay_policy_key_blacklist.html
   file_autoplay_policy_unmute_pauses.html
   file_autoplay_policy_activation_window.html
   file_autoplay_policy_activation_frame.html
   file_autoplay_policy_play_before_loadedmetadata.html
   flac-s24.flac
   flac-s24.flac^headers^
   flac-noheader-s16.flac
   flac-noheader-s16.flac^headers^
@@ -693,18 +692,16 @@ skip-if = true # bug 475110 - disabled s
 [test_autoplay_contentEditable.html]
 skip-if = android_version == '15' || android_version == '17' || android_version == '22' # android(bug 1232305, bug 1232318, bug 1372457)
 [test_autoplay_policy.html]
 skip-if = android_version == '23' # bug 1424903
 [test_autoplay_policy_activation.html]
 skip-if = android_version == '23' # bug 1424903
 [test_autoplay_policy_eventdown_activation.html]
 skip-if = android_version == '23' # bug 1424903
-[test_autoplay_policy_key_blacklist.html]
-skip-if = android_version == '23' # bug 1424903
 [test_autoplay_policy_unmute_pauses.html]
 skip-if = android_version == '23' # bug 1424903
 [test_autoplay_policy_play_before_loadedmetadata.html]
 skip-if = android_version == '23' # bug 1424903
 [test_buffered.html]
 skip-if = android_version == '15' || android_version == '22' # bug 1308388, android(bug 1232305)
 [test_bug448534.html]
 [test_bug463162.xhtml]
deleted file mode 100644
--- a/dom/media/test/test_autoplay_policy_key_blacklist.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-
-<head>
-  <title>Autoplay policy test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="AutoplayTestUtils.js"></script>
-</head>
-
-<body>
-  <pre id="test">
-      <script>
-
-        // Tests that keypresses for non-printable characters,
-        // and mouse/keyboard interaction with editable elements,
-        // don't gesture activate documents, and don't unblock
-        // audible autoplay.
-
-        gTestPrefs.push(["media.autoplay.enabled", false],
-          ["media.autoplay.enabled.user-gestures-needed", true]);
-
-        SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
-          runTest();
-        });
-
-        let child_url = "file_autoplay_policy_key_blacklist.html";
-
-        async function runTest() {
-          // Run test in a new window, to ensure its user gesture
-          // activation state isn't tainted by preceeding tests.
-          let child = window.open(child_url, "", "width=500,height=500");
-          await once(child, "load");
-          child.postMessage("run test", window.origin);
-          let result = await nextWindowMessage();
-          child.close();
-          SimpleTest.finish();
-        }
-
-        SimpleTest.waitForExplicitFinish();
-
-      </script>
-    </pre>
-</body>
-
-</html>
\ No newline at end of file
--- a/dom/media/webaudio/AudioBuffer.cpp
+++ b/dom/media/webaudio/AudioBuffer.cpp
@@ -326,17 +326,17 @@ AudioBuffer::CopyFromChannel(const Float
 {
   aDestination.ComputeLengthAndData();
 
   uint32_t length = aDestination.Length();
   CheckedInt<uint32_t> end = aStartInChannel;
   end += length;
   if (aChannelNumber >= NumberOfChannels() ||
       !end.isValid() || end.value() > Length()) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   JS::AutoCheckCannotGC nogc;
   JSObject* channelArray = mJSChannels[aChannelNumber];
   if (channelArray) {
     if (JS_GetTypedArrayLength(channelArray) != Length()) {
       // The array's buffer was detached.
@@ -370,17 +370,17 @@ AudioBuffer::CopyToChannel(JSContext* aJ
 {
   aSource.ComputeLengthAndData();
 
   uint32_t length = aSource.Length();
   CheckedInt<uint32_t> end = aStartInChannel;
   end += length;
   if (aChannelNumber >= NumberOfChannels() ||
       !end.isValid() || end.value() > Length()) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   if (!RestoreJSChannelData(aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
@@ -401,17 +401,17 @@ AudioBuffer::CopyToChannel(JSContext* aJ
 }
 
 void
 AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
                             JS::MutableHandle<JSObject*> aRetval,
                             ErrorResult& aRv)
 {
   if (aChannel >= NumberOfChannels()) {
-    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   if (!RestoreJSChannelData(aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -231,56 +231,45 @@ impl<'a> BuiltDisplayListIter<'a> {
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = (ItemRange::default(), 0);
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
-            self.next_raw();
-            if let SetGradientStops = self.cur_item.item {
-                // SetGradientStops is a dummy item that most consumers should ignore
-                continue;
+            if self.data.is_empty() {
+                return None;
             }
-            break;
-        }
 
-        Some(self.as_ref())
-    }
+            {
+                let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
+                bincode::deserialize_in_place(reader, &mut self.cur_item)
+                    .expect("MEH: malicious process?");
+            }
 
-    /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking
-    /// and may leave irrelevant ranges live (so a Clip may have GradientStops if
-    /// for some reason you ask).
-    pub fn next_raw<'b>(&'b mut self) -> Option<DisplayItemRef<'a, 'b>> {
-        use SpecificDisplayItem::*;
+            match self.cur_item.item {
+                SetGradientStops => {
+                    self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
 
-        if self.data.is_empty() {
-            return None;
-        }
-
-        {
-            let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
-            bincode::deserialize_in_place(reader, &mut self.cur_item)
-                .expect("MEH: malicious process?");
-        }
+                    // This is a dummy item, skip over it
+                    continue;
+                }
+                ClipChain(_) => {
+                    self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
+                }
+                Clip(_) | ScrollFrame(_) => {
+                    self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
+                }
+                Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
+                PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
+                _ => { /* do nothing */ }
+            }
 
-        match self.cur_item.item {
-            SetGradientStops => {
-                self.cur_stops = skip_slice::<GradientStop>(self.list, &mut self.data).0;
-            }
-            ClipChain(_) => {
-                self.cur_clip_chain_items = skip_slice::<ClipId>(self.list, &mut self.data).0;
-            }
-            Clip(_) | ScrollFrame(_) => {
-                self.cur_complex_clip = self.skip_slice::<ComplexClipRegion>()
-            }
-            Text(_) => self.cur_glyphs = self.skip_slice::<GlyphInstance>().0,
-            PushStackingContext(_) => self.cur_filters = self.skip_slice::<FilterOp>().0,
-            _ => { /* do nothing */ }
+            break;
         }
 
         Some(self.as_ref())
     }
 
     fn skip_slice<T: for<'de> Deserialize<'de>>(&mut self) -> (ItemRange<T>, usize) {
         skip_slice::<T>(self.list, &mut self.data)
     }
@@ -440,17 +429,17 @@ impl<'a, T: for<'de> Deserialize<'de>> :
 #[cfg(feature = "serialize")]
 impl Serialize for BuiltDisplayList {
     fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
         use display_item::CompletelySpecificDisplayItem::*;
         use display_item::GenericDisplayItem;
 
         let mut seq = serializer.serialize_seq(None)?;
         let mut traversal = self.iter();
-        while let Some(item) = traversal.next_raw() {
+        while let Some(item) = traversal.next() {
             let display_item = item.display_item();
             let serial_di = GenericDisplayItem {
                 item: match display_item.item {
                     SpecificDisplayItem::Clip(v) => Clip(
                         v,
                         item.iter.list.get(item.iter.cur_complex_clip.0).collect()
                     ),
                     SpecificDisplayItem::ClipChain(v) => ClipChain(
@@ -892,17 +881,17 @@ impl DisplayListBuilder {
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
         mem::swap(&mut temp.data, &mut self.data);
 
         {
             let mut iter = BuiltDisplayListIter::new(&temp);
-            while let Some(item) = iter.next_raw() {
+            while let Some(item) = iter.next() {
                 println!("{:?}", item.display_item());
             }
         }
 
         self.data = temp.data;
     }
 
     fn push_item(&mut self, item: SpecificDisplayItem, info: &LayoutPrimitiveInfo) {
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -349,57 +349,55 @@ ArrayLengthReadError(const char* aElemen
 }
 
 void
 SentinelReadError(const char* aClassName)
 {
   MOZ_CRASH_UNSAFE_PRINTF("incorrect sentinel when reading %s", aClassName);
 }
 
-void
+bool
 StateTransition(bool aIsDelete, State* aNext)
 {
   switch (*aNext) {
     case State::Null:
       if (aIsDelete) {
         *aNext = State::Dead;
       }
       break;
     case State::Dead:
-      LogicError("__delete__()d actor");
-      break;
+      return false;
     default:
-      LogicError("corrupted actor state");
-      break;
+      return false;
   }
+  return true;
 }
 
-void
+bool
 ReEntrantDeleteStateTransition(bool aIsDelete,
                                bool aIsDeleteReply,
                                ReEntrantDeleteState* aNext)
 {
   switch (*aNext) {
     case ReEntrantDeleteState::Null:
       if (aIsDelete) {
         *aNext = ReEntrantDeleteState::Dying;
       }
       break;
     case ReEntrantDeleteState::Dead:
-      LogicError("__delete__()d actor");
-      break;
+      return false;
     case ReEntrantDeleteState::Dying:
       if (aIsDeleteReply) {
         *aNext = ReEntrantDeleteState::Dead;
       }
       break;
     default:
-      LogicError("corrupted actor state");
-      break;
+      return false;
   }
+  return true;
 }
 
 void
 TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable,
              nsTArray<void*>& aArray)
 {
   uint32_t i = 0;
   void** elements = aArray.AppendElements(aTable.Count());
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -752,28 +752,28 @@ void AnnotateSystemError();
 
 enum class State
 {
   Dead,
   Null,
   Start = Null
 };
 
-void
+bool
 StateTransition(bool aIsDelete, State* aNext);
 
 enum class ReEntrantDeleteState
 {
   Dead,
   Null,
   Dying,
   Start = Null,
 };
 
-void
+bool
 ReEntrantDeleteStateTransition(bool aIsDelete,
                                bool aIsDeleteReply,
                                ReEntrantDeleteState* aNext);
 
 /**
  * An endpoint represents one end of a partially initialized IPDL channel. To
  * set up a new top-level protocol:
  *
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -405,16 +405,21 @@ def errfnRecv(msg, errcode=_Result.ValuE
 def errfnSentinel(rvalue=ExprLiteral.FALSE):
     def inner(msg):
         return [ _sentinelReadError(msg), StmtReturn(rvalue) ]
     return inner
 
 def _destroyMethod():
     return ExprVar('ActorDestroy')
 
+def errfnUnreachable(msg):
+    return [
+        _logicError(msg)
+    ]
+
 class _DestroyReason:
     @staticmethod
     def Type():  return Type('ActorDestroyReason')
 
     Deletion = ExprVar('Deletion')
     AncestorDeletion = ExprVar('AncestorDeletion')
     NormalShutdown = ExprVar('NormalShutdown')
     AbnormalShutdown = ExprVar('AbnormalShutdown')
@@ -3908,17 +3913,17 @@ class _GenerateProtocolActorCode(ipdl.as
         ifsendok = StmtIf(ExprLiteral.FALSE)
         ifsendok.addifstmts(destmts)
         ifsendok.addifstmts([ Whitespace.NL,
                               StmtExpr(ExprAssn(sendok, ExprLiteral.FALSE, '&=')) ])
 
         method.addstmt(ifsendok)
 
         if self.protocol.decl.type.hasReentrantDelete:
-            method.addstmts(self.transition(md, actor.var(), reply=True))
+            method.addstmts(self.transition(md, actor.var(), reply=True, errorfn=errfnUnreachable))
 
         method.addstmts(
             self.dtorEpilogue(md, actor.var())
             + [ Whitespace.NL, StmtReturn(sendok) ])
 
         return method
 
     def destroyActor(self, md, actorexpr, why=_DestroyReason.Deletion):
@@ -4053,17 +4058,17 @@ class _GenerateProtocolActorCode(ipdl.as
         actorhandle = self.handlevar
 
         stmts = self.deserializeMessage(md, self.side, errfnRecv,
                                         errfnSent=errfnSentinel(_Result.ValuError))
 
         idvar, saveIdStmts = self.saveActorId(md)
         case.addstmts(
             stmts
-            + self.transition(md)
+            + self.transition(md, errorfn=errfnRecv)
             + [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
                 for r in md.returns ]
             # alloc the actor, register it under the foreign ID
             + [ StmtExpr(ExprAssn(
                 actorvar,
                 self.callAllocActor(md, retsems='in', side=self.side))) ]
             + self.ctorPrologue(md, errfn=_Result.ValuError,
                                 idexpr=_actorHId(actorhandle))
@@ -4084,17 +4089,17 @@ class _GenerateProtocolActorCode(ipdl.as
         case = StmtBlock()
 
         stmts = self.deserializeMessage(md, self.side, errfnRecv,
                                         errfnSent=errfnSentinel(_Result.ValuError))
 
         idvar, saveIdStmts = self.saveActorId(md)
         case.addstmts(
             stmts
-            + self.transition(md)
+            + self.transition(md, errorfn=errfnRecv)
             + [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
                 for r in md.returns ]
             + self.invokeRecvHandler(md, implicit=0)
             + [ Whitespace.NL ]
             + saveIdStmts
             + self.makeReply(md, errfnRecv, routingId=idvar)
             + [ Whitespace.NL ]
             + self.genVerifyMessage(md.decl.type.verify, md.returns, errfnRecv,
@@ -4115,17 +4120,17 @@ class _GenerateProtocolActorCode(ipdl.as
 
         idvar, saveIdStmts = self.saveActorId(md)
         declstmts = [ StmtDecl(Decl(r.bareType(self.side), r.var().name))
                       for r in md.returns ]
         if md.decl.type.isAsync() and md.returns:
             declstmts = self.makeResolver(md, errfnRecv, routingId=idvar)
         case.addstmts(
             stmts
-            + self.transition(md)
+            + self.transition(md, errorfn=errfnRecv)
             + saveIdStmts
             + declstmts
             + self.invokeRecvHandler(md)
             + [ Whitespace.NL ]
             + self.makeReply(md, errfnRecv, routingId=idvar)
             + self.genVerifyMessage(md.decl.type.verify, md.returns, errfnRecv,
                                     self.replyvar)
             + [ StmtReturn(_Result.Processed) ])
@@ -4443,17 +4448,17 @@ class _GenerateProtocolActorCode(ipdl.as
     def sendAsync(self, md, msgexpr, actor=None):
         sendok = ExprVar('sendok__')
         resolvefn = ExprVar('aResolve')
         rejectfn = ExprVar('aReject')
 
         sendargs = [ msgexpr ]
         stmts = [ Whitespace.NL,
                   self.logMessage(md, msgexpr, 'Sending ', actor),
-                  self.profilerLabel(md) ] + self.transition(md, actor)
+                  self.profilerLabel(md) ] + self.transition(md, actor, errorfn=errfnUnreachable)
         stmts.append(Whitespace.NL)
 
         # Generate the actual call expression.
         send = ExprSelect(self.protocol.callGetChannel(actor), '->', 'Send')
         if md.returns:
             stmts.append(StmtExpr(ExprCall(send, args=[ msgexpr,
                                                         ExprVar('this'),
                                                         ExprMove(resolvefn),
@@ -4468,17 +4473,17 @@ class _GenerateProtocolActorCode(ipdl.as
 
     def sendBlocking(self, md, msgexpr, replyexpr, actor=None):
         sendok = ExprVar('sendok__')
         return (
             sendok,
             ([ Whitespace.NL,
                self.logMessage(md, msgexpr, 'Sending ', actor),
                self.profilerLabel(md) ]
-            + self.transition(md, actor)
+            + self.transition(md, actor, errorfn=errfnUnreachable)
             + [ Whitespace.NL,
                 StmtDecl(Decl(Type.BOOL, sendok.name)),
                 StmtBlock([
                     StmtExpr(ExprCall(ExprVar('AUTO_PROFILER_TRACING'),
                              [ ExprLiteral.String("IPC"),
                                ExprLiteral.String(self.protocol.name + "::" + md.prettyMsgName()) ])),
                     StmtExpr(ExprAssn(sendok,
                                       ExprCall(ExprSelect(self.protocol.callGetChannel(actor),
@@ -4622,17 +4627,17 @@ class _GenerateProtocolActorCode(ipdl.as
             # only save the ID if we're actually going to use it, to
             # avoid unused-variable warnings
             saveIdStmts = [ StmtDecl(Decl(_actorIdType(), idvar.name),
                                      self.protocol.routingId()) ]
         else:
             saveIdStmts = [ ]
         return idvar, saveIdStmts
 
-    def transition(self, md, actor=None, reply=False):
+    def transition(self, md, actor=None, reply=False, errorfn=None):
         msgid = md.pqMsgId() if not reply else md.pqReplyId()
         args = [
             ExprVar('true' if _deleteId().name == msgid else 'false'),
         ]
         if self.protocol.decl.type.hasReentrantDelete:
             function = 'ReEntrantDeleteStateTransition'
             args.append(
                 ExprVar('true' if _deleteReplyId().name == msgid else 'false'),
@@ -4642,19 +4647,19 @@ class _GenerateProtocolActorCode(ipdl.as
 
         if actor is not None:
             stateexpr = _actorState(actor)
         else:
             stateexpr = self.protocol.stateVar()
 
         args.append(ExprAddrOf(stateexpr))
 
-        return [
-            StmtExpr(ExprCall(ExprVar(function), args=args))
-        ]
+        ifstmt = StmtIf(ExprNot(ExprCall(ExprVar(function), args=args)))
+        ifstmt.addifstmts(errorfn('Transition error'))
+        return [ifstmt]
 
     def endRead(self, msgexpr, iterexpr):
         msgtype = ExprCall(ExprSelect(msgexpr, '.', 'type'), [ ])
         return StmtExpr(ExprCall(ExprSelect(msgexpr, '.', 'EndRead'),
                                  args=[ iterexpr, msgtype ]))
 
 class _GenerateProtocolParentCode(_GenerateProtocolActorCode):
     def __init__(self):
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -5420,31 +5420,25 @@ SweepCCWrappers(JSRuntime* runtime)
 static void
 SweepObjectGroups(JSRuntime* runtime)
 {
     for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next())
         c->objectGroups.sweep();
 }
 
 static void
-SweepRegExps(JSRuntime* runtime)
-{
-    for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next())
-        c->sweepRegExps();
-}
-
-static void
 SweepMisc(JSRuntime* runtime)
 {
     for (SweepGroupCompartmentsIter c(runtime); !c.done(); c.next()) {
         c->sweepGlobalObject();
         c->sweepTemplateObjects();
         c->sweepSavedStacks();
         c->sweepSelfHostingScriptSource();
         c->sweepNativeIterators();
+        c->sweepRegExps();
     }
 }
 
 static void
 SweepCompressionTasks(JSRuntime* runtime)
 {
     AutoLockHelperThreadState lock;
 
@@ -5710,17 +5704,16 @@ GCRuntime::beginSweepingSweepGroup(FreeO
         Maybe<AutoRunParallelTask> updateAtomsBitmap;
         if (sweepingAtoms)
             updateAtomsBitmap.emplace(rt, UpdateAtomsBitmap, PhaseKind::UPDATE_ATOMS_BITMAP, lock);
 
         AutoPhase ap(stats(), PhaseKind::SWEEP_COMPARTMENTS);
 
         AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers, PhaseKind::SWEEP_CC_WRAPPER, lock);
         AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups, PhaseKind::SWEEP_TYPE_OBJECT, lock);
-        AutoRunParallelTask sweepRegExps(rt, SweepRegExps, PhaseKind::SWEEP_REGEXP, lock);
         AutoRunParallelTask sweepMisc(rt, SweepMisc, PhaseKind::SWEEP_MISC, lock);
         AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks, PhaseKind::SWEEP_COMPRESSION, lock);
         AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps, PhaseKind::SWEEP_WEAKMAPS, lock);
         AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds, PhaseKind::SWEEP_UNIQUEIDS, lock);
 
         WeakCacheTaskVector sweepCacheTasks;
         if (!PrepareWeakCacheTasks(rt, &sweepCacheTasks))
             SweepWeakCachesOnMainThread(rt);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3762,23 +3762,47 @@ PresShell::GetRectVisibility(nsIFrame* a
   if (sf) {
     scrollPortRect = sf->GetScrollPortRect();
     nsIFrame* f = do_QueryFrame(sf);
     scrollPortRect += f->GetOffsetTo(rootFrame);
   } else {
     scrollPortRect = nsRect(nsPoint(0,0), rootFrame->GetSize());
   }
 
+  // scrollPortRect has the viewport visible area relative to rootFrame.
+  nsRect visibleAreaRect(scrollPortRect);
+  // Find the intersection of this and the frame's ancestor scrollable
+  // frames. We walk the whole ancestor chain to find all the scrollable
+  // frames.
+  nsIScrollableFrame* scrollAncestorFrame =
+    nsLayoutUtils::GetNearestScrollableFrame(aFrame,
+      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+  while (scrollAncestorFrame) {
+    nsRect scrollAncestorRect = scrollAncestorFrame->GetScrollPortRect();
+    nsIFrame* f = do_QueryFrame(scrollAncestorFrame);
+    scrollAncestorRect += f->GetOffsetTo(rootFrame);
+
+    visibleAreaRect = visibleAreaRect.Intersect(scrollAncestorRect);
+
+    // Continue up the chain.
+    scrollAncestorFrame =
+      nsLayoutUtils::GetNearestScrollableFrame(f->GetParent(),
+        nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+  }
+
+  // aRect is in the aFrame coordinate space, so bring it into rootFrame
+  // coordinate space.
   nsRect r = aRect + aFrame->GetOffsetTo(rootFrame);
   // If aRect is entirely visible then we don't need to ensure that
   // at least aMinTwips of it is visible
-  if (scrollPortRect.Contains(r))
+  if (visibleAreaRect.Contains(r)) {
     return nsRectVisibility_kVisible;
-
-  nsRect insetRect = scrollPortRect;
+  }
+
+  nsRect insetRect = visibleAreaRect;
   insetRect.Deflate(aMinTwips, aMinTwips);
   if (r.YMost() <= insetRect.y)
     return nsRectVisibility_kAboveViewport;
   if (r.y >= insetRect.YMost())
     return nsRectVisibility_kBelowViewport;
   if (r.XMost() <= insetRect.x)
     return nsRectVisibility_kLeftOfViewport;
   if (r.x >= insetRect.XMost())
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -825,27 +825,33 @@ public:
   virtual bool ScrollFrameRectIntoView(nsIFrame*     aFrame,
                                        const nsRect& aRect,
                                        ScrollAxis    aVertical,
                                        ScrollAxis    aHorizontal,
                                        uint32_t      aFlags) = 0;
 
   /**
    * Determine if a rectangle specified in the frame's coordinate system
-   * intersects the viewport "enough" to be considered visible.
+   * intersects "enough" with the viewport to be considered visible. This
+   * is not a strict test against the viewport -- it's a test against
+   * the intersection of the viewport and the frame's ancestor scrollable
+   * frames. If it doesn't intersect enough, return a value indicating
+   * which direction the frame's topmost ancestor scrollable frame would
+   * need to be scrolled to bring the frame into view.
    * @param aFrame frame that aRect coordinates are specified relative to
    * @param aRect rectangle in twips to test for visibility
-   * @param aMinTwips is the minimum distance in from the edge of the viewport
-   *                  that an object must be to be counted visible
+   * @param aMinTwips is the minimum distance in from the edge of the
+   *                  visible area that an object must be to be counted
+   *                  visible
    * @return nsRectVisibility_kVisible if the rect is visible
    *         nsRectVisibility_kAboveViewport
    *         nsRectVisibility_kBelowViewport
    *         nsRectVisibility_kLeftOfViewport
-   *         nsRectVisibility_kRightOfViewport rectangle is outside the viewport
-   *         in the specified direction
+   *         nsRectVisibility_kRightOfViewport rectangle is outside the
+   *         topmost ancestor scrollable frame in the specified direction
    */
   virtual nsRectVisibility GetRectVisibility(nsIFrame *aFrame,
                                              const nsRect &aRect,
                                              nscoord aMinTwips) const = 0;
 
   /**
    * Suppress notification of the frame manager that frames are
    * being destroyed.
--- a/mobile/locales/searchplugins/heureka-cz.xml
+++ b/mobile/locales/searchplugins/heureka-cz.xml
@@ -1,14 +1,14 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Heuréka</ShortName>
+<ShortName>Heureka</ShortName>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAQlBMVEX19fUAAAD19fX19fX19fX19fX/egT27eb+ghP9kTH9mUD6uH325tf41rn7qF75x5v+iSL33sj4z6r7sG38oE/5v4wVt/ymAAAABXRSTlPvAJ8gMC39vXwAAAKOSURBVGje7ZrdjqsgFIW1HQERgQK+/6ueTqMuBKKBQnImcd1Z0/11/6xtbOj6/vnohibqHs++7/ofhK+P+Om7Zzc01Dv8Y2iqR9cNTYX4rXQDbsANuAF/DSDmtyiuawLE4jRZNUpF6wLoMpJATtUD0ImRhLSqBFAIHyLmCgDKyYns14AXI6caaSkA8S8Ir0JAHF9LNdNfLyzO/5iJEkAcn5tD40e/SqUAqhE+mhfj3SwFuD3EkqB72U1lALNXOTXulngSBQAUiKXmZCa+eAlgIquSdg1201wAYGGBYzrE8wFqG8Lk7t7ohqELmQB+lrzbsttTsbkAepa72bxN92nVeQBUyJzNl/HGVeQA8D22Fd1yK0ILuM86WS9UDgAtcFhKqx8Qkgl/2qZMAL4GnKYHCyyHhvNMwHGG/JoswfxO3wNgusWzwOsI0HkAAcDBuK/dAjac2W8yQN21ggVWqRoAFIbAAlV6oMJCoN2bbBlAo9J+HFggdIwsM9qYfgIs/tIqM9qEFRO3AVj0eM4EzPhhcRteic2dt03hLUbjrGzCMC4bIBO15WCGCahsAHZm+CAw0aMH0GsAxBMdpQu3wr9myDMDgDZfTDgdkUA2ACmAEMdHAgUADL6kyfsjfJEDgNTpW4Zh8EUuAKO6S4qgQxz31FAAiPePM3QvvuIE0kMRAG2ERjm95cKXcnkBKH6JBSEfANkMQh4A01JIAOBC1JYRALiUkIks9CRGn1AGwGA64otJEw6ZLARA82L5R5NarXtOAKBI1wQAqhGmuoCYwCoDYgKtC4gJQ10ACHjwtACAMA9QFQAI8tfaZoBqAYCY8dz8n/8cvwEf3YAbcAMaqPmxh+YHN5ofPWl+eKb58Z9/o3DCvuyhQrcAAAAASUVORK5CYII=</Image>
 <Url type="application/x-suggestions+json" method="GET" template="http://www.heureka.cz/direct/firefox/autocompleter.php">
 	<Param name="query" value="{searchTerms}"/>
 </Url>
 <Url type="text/html" method="GET" template="http://www.heureka.cz/" resultdomain="heureka.cz">
 	<Param name="h[fraze]" value="{searchTerms}"/>
 	<Param name="utm_source" value="firefox-search"/>
new file mode 100644
--- /dev/null
+++ b/taskcluster/ci/addon/kind.yml
@@ -0,0 +1,39 @@
+# 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/.
+
+loader: taskgraph.loader.transform:loader
+
+transforms:
+    - taskgraph.transforms.job:transforms
+    - taskgraph.transforms.task:transforms
+
+jobs:
+    tps-xpi:
+        description: Build the TPS add-on
+        index:
+            product: firefox
+            job-name: addons.tps
+        treeherder:
+            platform: linux64/opt
+            symbol: TPS(addon)
+            kind: build
+            tier: 1
+        run-on-projects: [mozilla-central]
+        worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+        worker:
+            docker-image: {in-tree: debian7-amd64-build}
+            max-run-time: 1800
+            artifacts:
+                - type: file
+                  name: public/tps.xpi
+                  path: /builds/worker/checkouts/gecko/tps-out/tps.xpi
+        run:
+            using: run-task
+            command: >
+                cd /builds/worker/checkouts/gecko &&
+                ./mach tps-build --dest tps-out
+            sparse-profile: tps
+        when:
+            files-changed:
+                - 'services/sync/tps/extensions/tps/**'
--- a/taskcluster/ci/balrog/kind.yml
+++ b/taskcluster/ci/balrog/kind.yml
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.balrog_submit:transforms
    - taskgraph.transforms.scriptworker:add_balrog_scopes
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover
    - beetmover-l10n
    - beetmover-repackage
 
 only-for-attributes:
--- a/taskcluster/ci/beetmover-cdns/kind.yml
+++ b/taskcluster/ci/beetmover-cdns/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.beetmover_cdns:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-generate-checksums-beetmover
 
 job-defaults:
    run-on-projects: []
    shipping-phase: push
--- a/taskcluster/ci/beetmover-checksums/kind.yml
+++ b/taskcluster/ci/beetmover-checksums/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_checksums:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - checksums-signing
 
 only-for-attributes:
    - nightly
 
--- a/taskcluster/ci/beetmover-release-source-checksums/kind.yml
+++ b/taskcluster/ci/beetmover-release-source-checksums/kind.yml
@@ -2,16 +2,15 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_source_checksums:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-source-checksums-signing
 
 job-template:
    shipping-phase: promote
--- a/taskcluster/ci/beetmover-repackage/kind.yml
+++ b/taskcluster/ci/beetmover-repackage/kind.yml
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_repackage_l10n:transforms
    - taskgraph.transforms.beetmover_repackage:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage-signing
    - partials-signing
 
 only-for-build-platforms:
    - linux-nightly/opt
--- a/taskcluster/ci/beetmover-source/kind.yml
+++ b/taskcluster/ci/beetmover-source/kind.yml
@@ -3,16 +3,15 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover:transforms
    - taskgraph.transforms.beetmover_source:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-source-signing
 
 job-template:
    shipping-phase: promote
--- a/taskcluster/ci/beetmover/kind.yml
+++ b/taskcluster/ci/beetmover/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build-signing
 
 only-for-attributes:
    - nightly
 
--- a/taskcluster/ci/build-signing/kind.yml
+++ b/taskcluster/ci/build-signing/kind.yml
@@ -3,13 +3,12 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.build_signing:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.build_signing:transforms
    - taskgraph.transforms.signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build
--- a/taskcluster/ci/build/kind.yml
+++ b/taskcluster/ci/build/kind.yml
@@ -7,17 +7,16 @@ loader: taskgraph.loader.transform:loade
 kind-dependencies:
     - toolchain
 
 transforms:
     - taskgraph.transforms.build:transforms
     - taskgraph.transforms.build_attrs:transforms
     - taskgraph.transforms.build_lints:transforms
     - taskgraph.transforms.use_toolchains:transforms
-    - taskgraph.transforms.release_notifications:transforms
     - taskgraph.transforms.job:transforms
     - taskgraph.transforms.task:transforms
 
 jobs-from:
     - android.yml
     - android-stuff.yml
     - linux.yml
     - macosx.yml
--- a/taskcluster/ci/checksums-signing/kind.yml
+++ b/taskcluster/ci/checksums-signing/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.checksums_signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover
    - beetmover-l10n
    - beetmover-repackage
 
 only-for-attributes:
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -57,16 +57,17 @@ treeherder:
         'Searchfox': 'Searchfox builds'
         'SM': 'Spidermonkey builds'
         'pub': 'APK publishing'
         'p': 'Partial generation'
         'ps': 'Partials signing'
         'Rel': 'Release promotion'
         'Snap': 'Snap image generation'
         'langpack': 'Langpack sigatures and uploads'
+        'TPS': 'Sync tests'
 
 index:
     products:
         - 'firefox'
         - 'fennec'
         - 'mobile'
         - 'static-analysis'
         - 'devedition'
--- a/taskcluster/ci/google-play-strings/kind.yml
+++ b/taskcluster/ci/google-play-strings/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.google_play_strings:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 jobs:
    google-play-strings:
       description: Download strings to display on Google Play from https://l10n.mozilla-community.org/stores_l10n/
       attributes:
          build_type: google_play_strings
          build_platform: android-nightly
--- a/taskcluster/ci/nightly-l10n-signing/kind.yml
+++ b/taskcluster/ci/nightly-l10n-signing/kind.yml
@@ -3,16 +3,15 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.nightly_l10n_signing:transforms
    - taskgraph.transforms.signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - nightly-l10n
 
 only-for-attributes:
    - nightly
--- a/taskcluster/ci/nightly-l10n/kind.yml
+++ b/taskcluster/ci/nightly-l10n/kind.yml
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.l10n:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build
    - toolchain
 
 only-for-build-platforms:
    - linux64-nightly/opt
--- a/taskcluster/ci/post-beetmover-checksums-dummy/kind.yml
+++ b/taskcluster/ci/post-beetmover-checksums-dummy/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.reverse_chunk_deps:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover-checksums
    - beetmover-release-source-checksums
 
 jobs:
    firefox-promote:
--- a/taskcluster/ci/post-beetmover-dummy/kind.yml
+++ b/taskcluster/ci/post-beetmover-dummy/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.per_platform_dummy:transforms
    - taskgraph.transforms.reverse_chunk_deps:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover-checksums
    - beetmover-repackage
    # Fennec doesn't have beetmover-{checksums,repackage}, so
    # also depend on vanilla beetmover.
    - beetmover
--- a/taskcluster/ci/post-langpack-dummy/kind.yml
+++ b/taskcluster/ci/post-langpack-dummy/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.reverse_chunk_deps:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-beetmover-signed-langpacks
 
 jobs:
    firefox-promote:
       name: post-langpack-dummy
--- a/taskcluster/ci/push-apk/kind.yml
+++ b/taskcluster/ci/push-apk/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.push_apk:loader
 
 transforms:
    - taskgraph.transforms.push_apk:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build-signing
    - google-play-strings
    - beetmover-checksums
 
 jobs:
--- a/taskcluster/ci/release-balrog-scheduling/kind.yml
+++ b/taskcluster/ci/release-balrog-scheduling/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.scriptworker:add_balrog_scopes
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-bouncer-check
 
 job-defaults:
    run-on-projects: []
    shipping-phase: ship
--- a/taskcluster/ci/release-balrog-submit-toplevel/kind.yml
+++ b/taskcluster/ci/release-balrog-submit-toplevel/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.scriptworker:add_balrog_scopes
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    run-on-projects: []  # to make sure this never runs in CI
    shipping-phase: promote
    worker:
       implementation: balrog
       balrog-action: submit-toplevel
--- a/taskcluster/ci/release-beetmover-signed-langpacks/kind.yml
+++ b/taskcluster/ci/release-beetmover-signed-langpacks/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.release_beetmover_signed_addons:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-sign-and-push-langpacks
 
 only-for-attributes:
    - nightly
 
--- a/taskcluster/ci/release-binary-transparency/kind.yml
+++ b/taskcluster/ci/release-binary-transparency/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - post-beetmover-checksums-dummy
 
 job-defaults:
    name: release-binary-transparency
    description: Binary transparency to issue a certificate
--- a/taskcluster/ci/release-bouncer-aliases/kind.yml
+++ b/taskcluster/ci/release-bouncer-aliases/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.bouncer_aliases:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-bouncer-check
 
 job-defaults:
    description: Update bouncer aliases job
    worker-type:
--- a/taskcluster/ci/release-bouncer-check/kind.yml
+++ b/taskcluster/ci/release-bouncer-check/kind.yml
@@ -5,17 +5,16 @@
 loader: taskgraph.loader.transform:loader
 
 kind-dependencies:
     - beetmover-cdns
 
 transforms:
     - taskgraph.transforms.release_deps:transforms
     - taskgraph.transforms.bouncer_check:transforms
-    - taskgraph.transforms.release_notifications:transforms
     - taskgraph.transforms.job:transforms
     - taskgraph.transforms.task:transforms
 
 job-defaults:
     name: bouncer-check
     description: bouncer check
     run-on-projects: []  # to make sure this never runs as part of CI
     shipping-phase: push
--- a/taskcluster/ci/release-bouncer-sub/kind.yml
+++ b/taskcluster/ci/release-bouncer-sub/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.bouncer_submission:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    description: release bouncer submission job
    worker-type:
       by-project:
          mozilla-beta: scriptworker-prov-v1/bouncer-v1
          mozilla-release: scriptworker-prov-v1/bouncer-v1
--- a/taskcluster/ci/release-eme-free-repack-beetmover/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack-beetmover/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_repackage_partner:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-eme-free-repack-repackage-signing
 
 only-for-build-platforms:
    - macosx64-nightly/opt
    - win32-nightly/opt
--- a/taskcluster/ci/release-eme-free-repack-repackage-signing/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack-repackage-signing/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_signing_partner:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-eme-free-repack-repackage
 
 only-for-build-platforms:
    - win32-nightly/opt
    - win64-nightly/opt
--- a/taskcluster/ci/release-eme-free-repack-repackage/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack-repackage/kind.yml
@@ -5,17 +5,16 @@
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.chunk_partners:transforms
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_partner:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-eme-free-repack
    - release-eme-free-repack-signing
    - toolchain
 
 only-for-build-platforms:
--- a/taskcluster/ci/release-eme-free-repack-signing/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack-signing/kind.yml
@@ -4,17 +4,16 @@
 
 loader: taskgraph.loader.build_signing:loader
 
 transforms:
    - taskgraph.transforms.chunk_partners:transforms
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.partner_signing:transforms
    - taskgraph.transforms.signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-eme-free-repack
 
 only-for-build-platforms:
    - macosx64-nightly/opt
 
--- a/taskcluster/ci/release-eme-free-repack/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.partner_repack:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build-signing
    - nightly-l10n-signing
 
 job-defaults:
--- a/taskcluster/ci/release-final-verify/kind.yml
+++ b/taskcluster/ci/release-final-verify/kind.yml
@@ -6,17 +6,16 @@ loader: taskgraph.loader.transform:loade
 
 kind-dependencies:
    - release-bouncer-check
    - release-update-verify-config
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.final_verify:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: final-verify
    run-on-projects: []  # to make sure this never runs as part of CI
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
       implementation: docker-worker
--- a/taskcluster/ci/release-generate-checksums-beetmover/kind.yml
+++ b/taskcluster/ci/release-generate-checksums-beetmover/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.release_generate_checksums_beetmover:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-generate-checksums-signing
 
 job-template:
    shipping-phase: promote
    worker-type:
--- a/taskcluster/ci/release-generate-checksums-signing/kind.yml
+++ b/taskcluster/ci/release-generate-checksums-signing/kind.yml
@@ -4,13 +4,12 @@
 
 loader: taskgraph.loader.single_dep:loader
 
 kind-dependencies:
    - release-generate-checksums
 
 transforms:
    - taskgraph.transforms.release_generate_checksums_signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-template:
    shipping-phase: promote
--- a/taskcluster/ci/release-generate-checksums/kind.yml
+++ b/taskcluster/ci/release-generate-checksums/kind.yml
@@ -8,17 +8,16 @@ kind-dependencies:
    - beetmover-source
    - post-beetmover-checksums-dummy
    - release-beetmover-signed-langpacks
 
 transforms:
    - taskgraph.transforms.build:transforms
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_generate_checksums:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: generate-checksums
    description: generates checksums
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
--- a/taskcluster/ci/release-mark-as-shipped/kind.yml
+++ b/taskcluster/ci/release-mark-as-shipped/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_mark_as_shipped:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - push-apk
    - release-balrog-scheduling
    - release-bouncer-aliases
    - release-version-bump
 
--- a/taskcluster/ci/release-notify-promote/kind.yml
+++ b/taskcluster/ci/release-notify-promote/kind.yml
@@ -30,16 +30,17 @@ job-defaults:
          - echo "Dummy task"
    notifications:
       subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} is in the candidates directory"
       message: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} is in the candidates directory"
       emails:
          by-project:
             mozilla-beta: ["release-signoff@mozilla.org"]
             mozilla-release: ["release-signoff@mozilla.org"]
+            mozilla-esr60: ["release-signoff@mozilla.org"]
             try: ["{task_def[metadata][owner]}"]
             default: []
 
 jobs:
    fennec:
       shipping-product: fennec
    firefox:
       shipping-product: firefox
--- a/taskcluster/ci/release-notify-push/kind.yml
+++ b/taskcluster/ci/release-notify-push/kind.yml
@@ -29,16 +29,17 @@ job-defaults:
          - echo "Dummy task"
    notifications:
       subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has been pushed to cdntest"
       message: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has been pushed to cdntest"
       emails:
          by-project:
             mozilla-beta: ["release-signoff@mozilla.org"]
             mozilla-release: ["release-signoff@mozilla.org"]
+            mozilla-esr60: ["release-signoff@mozilla.org"]
             try: ["{task_def[metadata][owner]}"]
             default: []
 
 jobs:
    firefox:
       shipping-product: firefox
    devedition:
       shipping-product: devedition
--- a/taskcluster/ci/release-notify-ship/kind.yml
+++ b/taskcluster/ci/release-notify-ship/kind.yml
@@ -31,16 +31,17 @@ job-defaults:
          - /bin/bash
          - -c
          - echo "Dummy task"
    notifications:
       emails:
          by-project:
             mozilla-beta: ["release-signoff@mozilla.org"]
             mozilla-release: ["release-signoff@mozilla.org"]
+            mozilla-esr60: ["release-signoff@mozilla.org"]
             try: ["{task_def[metadata][owner]}"]
             default: []
 
 jobs:
    fennec:
       shipping-product: fennec
       notifications:
          subject: "{task[shipping-product]} {release_config[version]} build{release_config[build_number]}/{config[params][project]} has shipped!"
--- a/taskcluster/ci/release-partner-repack-beetmover/kind.yml
+++ b/taskcluster/ci/release-partner-repack-beetmover/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.beetmover_repackage_partner:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-partner-repack-repackage-signing
 
 only-for-build-platforms:
    - linux-nightly/opt
    - linux64-nightly/opt
--- a/taskcluster/ci/release-partner-repack-chunking-dummy/kind.yml
+++ b/taskcluster/ci/release-partner-repack-chunking-dummy/kind.yml
@@ -11,17 +11,16 @@ transforms:
    # We'd do it here, except single_dep doesn't pay attention to any
    # per platform things that we set.
    - taskgraph.transforms.copy_attributes_from_dependent_task:transforms
    # This transform is needed because task.py doesn't allow "dependent-task" to be
    # set, but the single_dep loader sets it (and we need it for chunk_partners,
    # name_sanity, and copy_build_platform_from_dependent_task to work).
    - taskgraph.transforms.strip_dependent_task:transforms
    - taskgraph.transforms.release_deps:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-partner-repack
 
 only-for-build-platforms:
    - linux-nightly/opt
    - linux64-nightly/opt
--- a/taskcluster/ci/release-partner-repack-repackage-signing/kind.yml
+++ b/taskcluster/ci/release-partner-repack-repackage-signing/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_signing_partner:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-partner-repack-chunking-dummy  # Linux
    - release-partner-repack-repackage  # Windows, Mac
 
 only-for-build-platforms:
    - linux-nightly/opt
--- a/taskcluster/ci/release-partner-repack-repackage/kind.yml
+++ b/taskcluster/ci/release-partner-repack-repackage/kind.yml
@@ -5,17 +5,16 @@
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.chunk_partners:transforms
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_partner:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-partner-repack
    - release-partner-repack-signing
    - toolchain
 
 only-for-build-platforms:
--- a/taskcluster/ci/release-partner-repack-signing/kind.yml
+++ b/taskcluster/ci/release-partner-repack-signing/kind.yml
@@ -4,17 +4,16 @@
 
 loader: taskgraph.loader.build_signing:loader
 
 transforms:
    - taskgraph.transforms.chunk_partners:transforms
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.partner_signing:transforms
    - taskgraph.transforms.signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-partner-repack
 
 only-for-build-platforms:
    - macosx64-nightly/opt
 
--- a/taskcluster/ci/release-partner-repack/kind.yml
+++ b/taskcluster/ci/release-partner-repack/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.partner_repack:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build-signing
    - nightly-l10n-signing
 
 job-defaults:
--- a/taskcluster/ci/release-secondary-balrog-scheduling/kind.yml
+++ b/taskcluster/ci/release-secondary-balrog-scheduling/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.worker_type:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - post-balrog-dummy
    - post-beetmover-dummy
    - release-secondary-balrog-submit-toplevel
 
 job-defaults:
--- a/taskcluster/ci/release-secondary-balrog-submit-toplevel/kind.yml
+++ b/taskcluster/ci/release-secondary-balrog-submit-toplevel/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.worker_type:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    run-on-projects: []  # to make sure this never runs in CI
    shipping-phase: promote
    worker-type:
       by-project:
          maple: scriptworker-prov-v1/balrog-dev
--- a/taskcluster/ci/release-secondary-final-verify/kind.yml
+++ b/taskcluster/ci/release-secondary-final-verify/kind.yml
@@ -8,17 +8,16 @@ kind-dependencies:
    - post-balrog-dummy
    - post-beetmover-dummy
    - release-secondary-balrog-submit-toplevel
    - release-secondary-update-verify-config
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.final_verify:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: secondary-final-verify
    run-on-projects: []  # to make sure this never runs as part of CI
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
       implementation: docker-worker
--- a/taskcluster/ci/release-secondary-update-verify-config/kind.yml
+++ b/taskcluster/ci/release-secondary-update-verify-config/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.update_verify_config:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: secondary-update-verify-config
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-product: firefox
    shipping-phase: promote
--- a/taskcluster/ci/release-secondary-update-verify/kind.yml
+++ b/taskcluster/ci/release-secondary-update-verify/kind.yml
@@ -8,17 +8,16 @@ kind-dependencies:
    - post-balrog-dummy
    - post-beetmover-dummy
    - release-secondary-balrog-submit-toplevel
    - release-secondary-update-verify-config
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.update_verify:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: secondary-update-verify
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
--- a/taskcluster/ci/release-sign-and-push-langpacks/kind.yml
+++ b/taskcluster/ci/release-sign-and-push-langpacks/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.release_sign_and_push_langpacks:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - build
    - nightly-l10n
 
 
 only-for-build-platforms:
--- a/taskcluster/ci/release-snap-push/kind.yml
+++ b/taskcluster/ci/release-snap-push/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_snap_push:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-snap-repackage
 
 job-defaults:
    description: Pushes (Ubuntu) Snaps onto Snap Store
    run-on-projects: []  # to make sure this never runs as part of CI
--- a/taskcluster/ci/release-snap-repackage/kind.yml
+++ b/taskcluster/ci/release-snap-repackage/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_snap_repackage:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - post-beetmover-dummy
    - post-langpack-dummy
 
 job-defaults:
    description: Generates snap image
--- a/taskcluster/ci/release-source-checksums-signing/kind.yml
+++ b/taskcluster/ci/release-source-checksums-signing/kind.yml
@@ -2,16 +2,15 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.source_checksums_signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover-source
 
 job-template:
    shipping-phase: promote
--- a/taskcluster/ci/release-source-signing/kind.yml
+++ b/taskcluster/ci/release-source-signing/kind.yml
@@ -3,16 +3,15 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.build_signing:transforms
    - taskgraph.transforms.signing:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-source
 
 job-template:
    shipping-phase: promote
--- a/taskcluster/ci/release-source/kind.yml
+++ b/taskcluster/ci/release-source/kind.yml
@@ -7,17 +7,16 @@ loader: taskgraph.loader.transform:loade
 kind-dependencies:
     - toolchain
 
 transforms:
     - taskgraph.transforms.build:transforms
     - taskgraph.transforms.build_attrs:transforms
     - taskgraph.transforms.build_lints:transforms
     - taskgraph.transforms.use_toolchains:transforms
-    - taskgraph.transforms.release_notifications:transforms
     - taskgraph.transforms.job:transforms
     - taskgraph.transforms.task:transforms
 
 
 job-defaults:
     shipping-phase: promote
     treeherder:
         symbol: Src
--- a/taskcluster/ci/release-update-verify-config/kind.yml
+++ b/taskcluster/ci/release-update-verify-config/kind.yml
@@ -1,17 +1,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.update_verify_config:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: update-verify-config
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
--- a/taskcluster/ci/release-update-verify/kind.yml
+++ b/taskcluster/ci/release-update-verify/kind.yml
@@ -8,17 +8,16 @@ kind-dependencies:
    - post-balrog-dummy
    - post-beetmover-dummy
    - release-balrog-submit-toplevel
    - release-update-verify-config
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.update_verify:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 job-defaults:
    name: update-verify
    run-on-projects: []  # to make sure this never runs as part of CI
    shipping-phase: promote
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
--- a/taskcluster/ci/release-version-bump/kind.yml
+++ b/taskcluster/ci/release-version-bump/kind.yml
@@ -2,17 +2,16 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
     - taskgraph.transforms.release_deps:transforms
     - taskgraph.transforms.release_version_bump:transforms
-    - taskgraph.transforms.release_notifications:transforms
     - taskgraph.transforms.task:transforms
 
 kind-dependencies:
     - beetmover-cdns
 
 job-defaults:
     description: Release Promotion version bump/tag
     run-on-projects: []
--- a/taskcluster/ci/repackage-l10n/kind.yml
+++ b/taskcluster/ci/repackage-l10n/kind.yml
@@ -5,17 +5,16 @@
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.repackage_l10n:transforms
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage:transforms
    - taskgraph.transforms.use_toolchains:transforms
    - taskgraph.transforms.job:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - nightly-l10n-signing
    - toolchain
 
 only-for-build-platforms:
    - linux-nightly/opt
--- a/taskcluster/ci/repackage-signing/kind.yml
+++ b/taskcluster/ci/repackage-signing/kind.yml
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
    - taskgraph.transforms.name_sanity:transforms
    - taskgraph.transforms.repackage_signing:transforms
    - taskgraph.transforms.repackage_routes:transforms
-   - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - repackage
    - repackage-l10n
 
 only-for-build-platforms:
    - linux-nightly/opt
--- a/taskcluster/ci/source-test/python.yml
+++ b/taskcluster/ci/source-test/python.yml
@@ -67,17 +67,17 @@ mochitest-harness:
             ./mach python-test --subsuite mochitest
     when:
         files-changed:
             - 'testing/mochitest/**'
             - 'testing/mozbase/moztest/moztest/selftest/**'
             - 'testing/mozharness/mozharness/base/log.py'
             - 'testing/mozharness/mozharness/mozilla/structuredlog.py'
             - 'testing/mozharness/mozharness/mozilla/testing/errors.py'
-            - 'testing/profiles/prefs_general.js'
+            - 'testing/profiles/**'
 
 mozbase:
     description: testing/mozbase unit tests
     treeherder:
         symbol: py(mb)
     run:
         mach: python-test --subsuite mozbase
     when:
--- a/taskcluster/docker/desktop1604-test/Dockerfile
+++ b/taskcluster/docker/desktop1604-test/Dockerfile
@@ -61,22 +61,16 @@ ENV           LOGNAME       worker
 ENV           HOSTNAME      taskcluster-worker
 ENV           LANG          en_US.UTF-8
 ENV           LC_ALL        en_US.UTF-8
 
 # Add utilities and configuration
 COPY           dot-files/config              /builds/worker/.config
 COPY           dot-files/pulse               /builds/worker/.pulse
 RUN            chmod +x bin/*
-# TODO: remove this when buildbot is gone
-COPY           buildprops.json               /builds/worker/buildprops.json
-
-# TODO: remove
-ADD            https://raw.githubusercontent.com/taskcluster/buildbot-step/master/buildbot_step /builds/worker/bin/buildbot_step
-RUN chmod u+x /builds/worker/bin/buildbot_step
 
 # allow the worker user to access video devices
 RUN usermod -a -G video worker
 
 RUN mkdir Documents; mkdir Pictures; mkdir Music; mkdir Videos; mkdir artifacts
 
 ENV PATH $PATH:/builds/worker/bin
 
--- a/taskcluster/docker/periodic-updates/README.md
+++ b/taskcluster/docker/periodic-updates/README.md
@@ -17,18 +17,19 @@ docker run -e DO_HSTS=1 -e DO_HPKP=1 -e 
 
 HSTS checks will only be run if the `DO_HSTS` environment variable is set.
 Likewise for `DO_HPKP` and the HPKP checks, and `DO_BLOCKLIST` and the
 blocklist checks. Environment variables are used rather than command line
 arguments to make constructing taskcluster tasks easier.
 
 ==Background==
 
-These scripts have been moved from `https://hg.mozilla.org/build/tools/scripts/periodic_file_updates/` and
-`security/manager/tools/` in the main repos, as part of the buildbot to taskcluster migration.
+These scripts have been moved from
+`https://hg.mozilla.org/build/tools/scripts/periodic_file_updates/` and
+`security/manager/tools/` in the main repos.
 
 ==HSTS Checks==
 
 `scripts/getHSTSPreloadList.js` will examine the current contents of
 nsSTSPreloadList.inc from whichever `BRANCH` is specified, add in the mandatory
 hosts, and those from the Chromium source, and check them all to see if their
 SSL configuration is valid, and whether or not they have the
 Strict-Transport-Security header set with an appropriate `max-age`. 
--- a/taskcluster/docker/recipes/ubuntu1604-test-system-setup.sh
+++ b/taskcluster/docker/recipes/ubuntu1604-test-system-setup.sh
@@ -89,17 +89,17 @@ apt-get update
 export DEBIAN_FRONTEND=noninteractive
 apt-get install -y -f "${apt_packages[@]}"
 
 dpkg-reconfigure locales
 
 . /setup/common.sh
 . /setup/install-mercurial.sh
 
-pip install --upgrade pip
+pip install --upgrade 'pip<10.0'
 
 pip install virtualenv
 
 . /setup/install-node.sh
 
 # Install custom-built Debian packages.  These come from a set of repositories
 # packaged in tarballs on tooltool to make them replicable.  Because they have
 # inter-dependenices, we install all repositories first, then perform the
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -457,8 +457,12 @@ packages
 --------
 Tasks used to build packages for use in docker images.
 
 diffoscope
 ----------
 Tasks used to compare pairs of Firefox builds using https://diffoscope.org/.
 As of writing, this is mainly meant to be used in try builds, by editing
 taskcluster/ci/diffoscope/kind.yml for your needs.
+
+addon
+-----
+Tasks used to build/package add-ons.
--- a/taskcluster/docs/transforms.rst
+++ b/taskcluster/docs/transforms.rst
@@ -132,17 +132,16 @@ verbatim, although it is augmented by th
 The run-using implementations are all located in
 ``taskcluster/taskgraph/transforms/job``, along with the schemas for their
 implementations.  Those well-commented source files are the canonical
 documentation for what constitutes a job description, and should be considered
 part of the documentation.
 
 following ``run-using`` are available
 
-  * ``buildbot``
   * ``hazard``
   * ``mach``
   * ``mozharness``
   * ``mozharness-test``
   * ``run-task``
   * ``spidermonkey`` or ``spidermonkey-package`` or ``spidermonkey-mozjs-crate`` or ``spidermonkey-rust-bindings``
   * ``debian-package``
   * ``toolchain-script``
--- a/taskcluster/taskgraph/morph.py
+++ b/taskcluster/taskgraph/morph.py
@@ -98,18 +98,16 @@ def derive_misc_task(task, purpose, imag
     task.task_id = slugid()
     return task
 
 
 # these regular expressions capture route prefixes for which we have a star
 # scope, allowing them to be summarized.  Each should correspond to a star scope
 # in each Gecko `assume:repo:hg.mozilla.org/...` role.
 SCOPE_SUMMARY_REGEXPS = [
-    re.compile(r'(index:insert-task:buildbot\.branches\.[^.]*\.).*'),
-    re.compile(r'(index:insert-task:buildbot\.revisions\.).*'),
     re.compile(r'(index:insert-task:docker\.images\.v1\.[^.]*\.).*'),
     re.compile(r'(index:insert-task:gecko\.v2\.[^.]*\.).*'),
 ]
 
 
 def make_index_task(parent_task, taskgraph, label_to_taskid):
     index_paths = [r.split('.', 1)[1] for r in parent_task.task['routes']
                    if r.startswith('index.')]
@@ -156,100 +154,16 @@ def add_index_tasks(taskgraph, label_to_
     if added:
         taskgraph, label_to_taskid = amend_taskgraph(
             taskgraph, label_to_taskid, added)
         logger.info('Added {} index tasks'.format(len(added)))
 
     return taskgraph, label_to_taskid
 
 
-def make_s3_uploader_task(parent_task):
-    if parent_task.task['payload']['sourcestamp']['branch'] == 'try':
-        worker_type = 'buildbot-try'
-    else:
-        worker_type = 'buildbot'
-
-    task_def = {
-        # The null-provisioner and buildbot worker type don't actually exist.
-        # So this task doesn't actually run - we just need to create the task so
-        # we have something to attach artifacts to.
-        "provisionerId": "null-provisioner",
-        "workerType": worker_type,
-        "created": {'relative-datestamp': '0 seconds'},
-        "deadline": parent_task.task['deadline'],
-        "routes": parent_task.task['routes'],
-        "payload": {},
-        "extra": {
-            "index": {
-                "rank": 1493912914,
-            }
-        },
-        "metadata": {
-            "name": "Buildbot/mozharness S3 uploader",
-            "description": "Upload outputs of buildbot/mozharness builds to S3",
-            "owner": "mshal@mozilla.com",
-            "source": "http://hg.mozilla.org/build/mozharness/",
-        }
-    }
-    parent_task.task['routes'] = []
-    label = 's3-uploader-{}'.format(parent_task.label)
-    dependencies = {}
-    task = Task(kind='misc', label=label, attributes={}, task=task_def,
-                dependencies=dependencies)
-    task.task_id = parent_task.task['payload']['properties']['upload_to_task_id']
-    return task
-
-
-def update_test_tasks(taskid, build_taskid, taskgraph):
-    """Tests task must download artifacts from uploader task."""
-    # Notice we handle buildbot-bridge, native, and generic-worker payloads
-    # We can do better here in terms of graph searching
-    # We could do post order search and stop as soon as we
-    # reach the build task. Not worring about it because this is
-    # (supposed to be) a temporary solution.
-    for task in taskgraph.tasks.itervalues():
-        if build_taskid in task.task.get('dependencies', []):
-            payload = task.task['payload']
-            task.task['dependencies'].append(taskid)
-            taskgraph.graph.edges.add((task.task_id, taskid, 'uploader'))
-            if 'command' in payload:
-                try:
-                    payload['command'] = [
-                        cmd.replace(build_taskid, taskid) for cmd in payload['command']
-                    ]
-                except AttributeError:
-                    # generic-worker command attribute is an list of lists
-                    payload['command'] = [
-                        [cmd.replace(build_taskid, taskid) for cmd in x]
-                        for x in payload['command']
-                    ]
-            if 'mounts' in payload:
-                for mount in payload['mounts']:
-                    if mount.get('content', {}).get('taskId', '') == build_taskid:
-                        mount['content']['taskId'] = taskid
-            if 'env' in payload:
-                payload['env'] = {
-                    k: v.replace(build_taskid, taskid) for k, v in payload['env'].iteritems()
-                }
-            if 'properties' in payload:
-                payload['properties']['parent_task_id'] = taskid
-
-
-def add_s3_uploader_task(taskgraph, label_to_taskid):
-    """The S3 uploader task is used by mozharness to upload buildbot artifacts."""
-    for task in taskgraph.tasks.itervalues():
-        if 'upload_to_task_id' in task.task.get('payload', {}).get('properties', {}):
-            added = make_s3_uploader_task(task)
-            taskgraph, label_to_taskid = amend_taskgraph(
-                taskgraph, label_to_taskid, [added])
-            update_test_tasks(added.task_id, task.task_id, taskgraph)
-            logger.info('Added s3-uploader task for %s' % task.task_id)
-    return taskgraph, label_to_taskid
-
-
 class apply_jsone_templates(object):
     """Apply a set of JSON-e templates to each task's `task` attribute.
 
     :param templates: A dict with the template name as the key, and extra context
                       to use (in addition to task.to_json()) as the value.
     """
     template_dir = os.path.join(here, 'templates')
 
@@ -285,16 +199,15 @@ class apply_jsone_templates(object):
 
         return taskgraph, label_to_taskid
 
 
 def morph(taskgraph, label_to_taskid, parameters):
     """Apply all morphs"""
     morphs = [
         add_index_tasks,
-        add_s3_uploader_task,
     ]
     if parameters['try_mode'] == 'try_task_config':
         morphs.append(apply_jsone_templates(parameters['try_task_config']))
 
     for m in morphs:
         taskgraph, label_to_taskid = m(taskgraph, label_to_taskid)
     return taskgraph, label_to_taskid
--- a/taskcluster/taskgraph/optimize.py
+++ b/taskcluster/taskgraph/optimize.py
@@ -317,32 +317,24 @@ class IndexSearch(OptimizationStrategy):
                 # 404 will end up here and go on to the next index path
                 pass
 
         return False
 
 
 class SETA(OptimizationStrategy):
     def should_remove_task(self, task, params, _):
-        bbb_task = False
-
-        # for bbb tasks we need to send in the buildbot buildername
-        if task.task.get('provisionerId', '') == 'buildbot-bridge':
-            label = task.task.get('payload').get('buildername')
-            bbb_task = True
-        else:
-            label = task.label
+        label = task.label
 
         # we would like to return 'False, None' while it's high_value_task
         # and we wouldn't optimize it. Otherwise, it will return 'True, None'
         if is_low_value_task(label,
                              params.get('project'),
                              params.get('pushlog_id'),
-                             params.get('pushdate'),
-                             bbb_task):
+                             params.get('pushdate')):
             # Always optimize away low-value tasks
             return True
         else:
             return False
 
 
 class SkipUnlessChanged(OptimizationStrategy):
     def should_remove_task(self, task, params, file_patterns):
deleted file mode 100644
--- a/taskcluster/taskgraph/transforms/job/buildbot.py
+++ /dev/null
@@ -1,108 +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/.
-"""
-
-Support for running jobs via buildbot.
-
-"""
-
-from __future__ import absolute_import, print_function, unicode_literals
-import slugid
-from urlparse import urlparse
-
-from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
-from taskgraph.util.scriptworker import get_release_config
-from voluptuous import Optional, Required, Any
-
-from taskgraph.transforms.job import run_job_using
-
-
-buildbot_run_schema = Schema({
-    Required('using'): 'buildbot',
-
-    # the buildername to use for buildbot-bridge, will expand {branch} in name from
-    # the current project.
-    Required('buildername'): basestring,
-
-    # the product to use
-    Required('product'): Any('firefox', 'mobile', 'fennec', 'devedition', 'thunderbird'),
-
-    Optional('channels'): optionally_keyed_by('project', basestring),
-
-    Optional('release-promotion'): bool,
-
-    Optional('release-eta'): basestring,
-})
-
-
-def _get_balrog_api_root(branch):
-    if branch in ('mozilla-beta', 'mozilla-release') or branch.startswith('mozilla-esr'):
-        return 'https://aus4-admin.mozilla.org/api'
-    else:
-        return 'https://balrog-admin.stage.mozaws.net/api'
-
-
-def bb_release_worker(config, worker, run):
-    # props
-    release_props = get_release_config(config)
-    repo_path = urlparse(config.params['head_repository']).path.lstrip('/')
-    revision = config.params['head_rev']
-    branch = config.params['project']
-    product = run['product']
-
-    release_props.update({
-        'release_promotion': True,
-        'repo_path': repo_path,
-        'revision': revision,
-    })
-
-    if 'channels' in run:
-        release_props['channels'] = run['channels']
-        resolve_keyed_by(release_props, 'channels', 'channels', **config.params)
-
-    if product in ('devedition', 'firefox'):
-        release_props['balrog_api_root'] = _get_balrog_api_root(branch)
-
-    if run.get('release-eta'):
-        # TODO Use same property name when we move away from BuildBot
-        release_props['schedule_at'] = run['release-eta']
-
-    worker['properties'].update(release_props)
-    # Setting script_repo_revision to the gecko revision doesn't work for
-    # jobs that clone build/tools or other repos instead of gecko.
-    if 'script_repo_revision' not in worker['properties']:
-        worker['properties']['script_repo_revision'] = revision
-
-
-def bb_ci_worker(config, worker):
-    worker['properties'].update({
-        'who': config.params['owner'],
-        'upload_to_task_id': slugid.nice(),
-    })
-
-
-@run_job_using('buildbot-bridge', 'buildbot', schema=buildbot_run_schema)
-def mozharness_on_buildbot_bridge(config, job, taskdesc):
-    run = job['run']
-    worker = taskdesc['worker']
-    branch = config.params['project']
-    product = run['product']
-
-    buildername = run['buildername'].format(branch=branch)
-    revision = config.params['head_rev']
-
-    worker.update({
-        'buildername': buildername,
-        'sourcestamp': {
-            'branch': branch,
-            'repository': config.params['head_repository'],
-            'revision': revision,
-        },
-    })
-    worker.setdefault('properties', {})['product'] = product
-
-    if run.get('release-promotion'):
-        bb_release_worker(config, worker, run)
-    else:
-        bb_ci_worker(config, worker)
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -418,126 +418,8 @@ def mozharness_test_on_native_engine(con
             for i, c in enumerate(command):
                 if isinstance(c, basestring) and c.startswith('--test-suite'):
                     command[i] += suffix
 
     if 'download-symbols' in mozharness:
         download_symbols = mozharness['download-symbols']
         download_symbols = {True: 'true', False: 'false'}.get(download_symbols, download_symbols)
         command.append('--download-symbols=' + download_symbols)
-
-
-@run_job_using('buildbot-bridge', 'mozharness-test', schema=mozharness_test_run_schema)
-def mozharness_test_buildbot_bridge(config, job, taskdesc):
-    test = taskdesc['run']['test']
-    mozharness = test['mozharness']
-    worker = taskdesc['worker']
-
-    branch = config.params['project']
-    build_platform, build_type = test['build-platform'].split('/')
-    test_platform = test['test-platform'].split('/')[0]
-    test_name = test.get('try-name', test['test-name'])
-    mozharness = test['mozharness']
-
-    # mochitest e10s follows the pattern mochitest-e10s-<suffix>
-    # in buildbot, except for these special cases
-    buildbot_specials = [
-        'mochitest-webgl',
-        'mochitest-clipboard',
-        'mochitest-media',
-        'mochitest-gpu',
-        'mochitest-e10s',
-    ]
-    test_name = test.get('try-name', test['test-name'])
-    if test['e10s'] and 'e10s' not in test_name:
-        test_name += '-e10s'
-
-    if test_name.startswith('mochitest') \
-            and test_name.endswith('e10s') \
-            and not any(map(
-                lambda name: test_name.startswith(name),
-                buildbot_specials
-            )):
-        split_mochitest = test_name.split('-')
-        test_name = '-'.join([
-            split_mochitest[0],
-            split_mochitest[-1],
-            '-'.join(split_mochitest[1:-1])
-        ])
-
-    # in buildbot, mochitest-webgl is called mochitest-gl
-    test_name = test_name.replace('webgl', 'gl')
-
-    if mozharness.get('chunked', False):
-        this_chunk = test.get('this-chunk')
-        test_name = '{}-{}'.format(test_name, this_chunk)
-    elif test.get('this-chunk', 1) != 1:
-        raise Exception("Unexpected chunking when 'chunked' attribute is 'false'"
-                        " for {}".format(test_name))
-
-    if test.get('suite', '') == 'talos':
-        variant = get_variant(test['test-platform'])
-
-        # On beta and release, we run nightly builds on-push; the talos
-        # builders need to run against non-nightly buildernames
-        if variant == 'nightly':
-            variant = ''
-
-        # this variant name has branch after the variant type in BBB bug 1338871
-        if variant in ('qr', 'stylo', 'stylo-sequential', 'devedition', 'stylo-disabled'):
-            name = '{prefix} {variant} {branch} talos {test_name}'
-        elif variant:
-            name = '{prefix} {branch} {variant} talos {test_name}'
-        else:
-            name = '{prefix} {branch} talos {test_name}'
-
-        buildername = name.format(
-            prefix=BUILDER_NAME_PREFIX[test_platform],
-            variant=variant,
-            branch=branch,
-            test_name=test_name
-        )
-
-        if buildername.startswith('Ubuntu'):
-            buildername = buildername.replace('VM', 'HW')
-    else:
-        variant = get_variant(test['test-platform'])
-        # If we are a pgo type, munge the build_type for the
-        # Unittest builder name generation
-        if 'pgo' in variant:
-            build_type = variant
-        prefix = BUILDER_NAME_PREFIX.get(
-            (test_platform, test.get('virtualization')),
-            BUILDER_NAME_PREFIX[test_platform])
-        if variant in ['stylo-disabled']:
-            buildername = '{prefix} {variant} {branch} {build_type} test {test_name}'.format(
-                prefix=prefix,
-                variant=variant,
-                branch=branch,
-                build_type=build_type,
-                test_name=test_name
-            )
-        else:
-            buildername = '{prefix} {branch} {build_type} test {test_name}'.format(
-                prefix=prefix,
-                branch=branch,
-                build_type=build_type,
-                test_name=test_name
-            )
-
-    worker.update({
-        'buildername': buildername,
-        'sourcestamp': {
-            'branch': branch,
-            'repository': config.params['head_repository'],
-            'revision': config.params['head_rev'],
-        },
-        'properties': {
-            'product': test.get('product', 'firefox'),
-            'who': config.params['owner'],
-            'installer_path': mozharness['build-artifact-name'],
-        }
-    })
-
-    if mozharness['requires-signed-builds']:
-        upstream_task = '<build-signing>'
-        installer_url = get_artifact_url(upstream_task, mozharness['build-artifact-name'])
-        worker['properties']['signed_installer_url'] = {'task-reference': installer_url}
--- a/taskcluster/taskgraph/transforms/release_notifications.py
+++ b/taskcluster/taskgraph/transforms/release_notifications.py
@@ -3,90 +3,49 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Add notifications via taskcluster-notify for release tasks
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.scriptworker import get_release_config, RELEASE_NOTIFICATION_PHASES
+from taskgraph.util.scriptworker import get_release_config
 from taskgraph.util.schema import resolve_keyed_by
 
 
 transforms = TransformSequence()
 
-EMAIL_DESTINATIONS = {
-    'mozilla-beta': ["release-automation-notifications@mozilla.com"],
-    'mozilla-release': ["release-automation-notifications@mozilla.com"],
-    'mozilla-esr60': ["release-automation-notifications@mozilla.com"],
-    # otherwise []
-}
-
-# Only notify on tasks that have issues
-DEFAULT_ROUTES = [
-    'notify.email.{email_dest}.on-failed',
-    'notify.email.{email_dest}.on-exception',
-]
-
-SUBJECT_TEMPLATE = "${{status.state}}: [{shipping_product} {release_config[version]} " + \
-                   "build{release_config[build_number]}/{config[params][project]}] {label}"
-
 
 @transforms.add
 def add_notifications(config, jobs):
     release_config = get_release_config(config)
-    email_dest = EMAIL_DESTINATIONS.get(config.params['project'], [])
 
     for job in jobs:
-        # Frankly, my dear, you're all over the place
-        shipping_phase = job.get('attributes', {}).get('shipping_phase') or \
-            job.get('shipping-phase')
-        shipping_product = job.get('attributes', {}).get('shipping_product') or \
-            job.get('shipping-product')
-        label = job.get('label') or '{}-{}'.format(config.kind, job['name'])
+        label = '{}-{}'.format(config.kind, job['name'])
 
-        # Handle notification overrides
         notifications = job.get('notifications')
         if notifications:
             resolve_keyed_by(notifications, 'emails', label, project=config.params['project'])
             emails = notifications['emails']
             format_kwargs = dict(
                 task=job,
                 config=config.__dict__,
                 release_config=release_config,
             )
             subject = notifications['subject'].format(**format_kwargs)
             message = notifications['message'].format(**format_kwargs)
-            # we only send these on succces to avoid messages like 'blah is in the
-            # candidates dir' when cancelling graphs, dummy job failure, etc
-            routes = ['notify.email.{email_dest}.on-completed']
             # Don't need this any more
             del job['notifications']
-        else:
-            emails = email_dest
-            format_kwargs = dict(
-                label=label,
-                shipping_product=shipping_product,
-                config=config.__dict__,
-                release_config=release_config,
+
+            # We only send mail on success to avoid messages like 'blah is in the
+            # candidates dir' when cancelling graphs, dummy job failure, etc
+            job.setdefault('routes', []).extend(
+                ['notify.email.{}.on-completed'.format(email) for email in emails]
             )
-            subject = SUBJECT_TEMPLATE.format(**format_kwargs)
-            message = None
-            routes = DEFAULT_ROUTES
-
-        # We only modify release jobs, or nightly & release being run in the context of a release
-        if shipping_phase in RELEASE_NOTIFICATION_PHASES and \
-                config.params['target_tasks_method'].startswith(RELEASE_NOTIFICATION_PHASES):
-
-            # Add routes to trigger notifications via tc-notify
-            for dest in emails:
-                job.setdefault('routes', []).extend(
-                    [r.format(email_dest=dest) for r in routes]
-                )
 
             # Customize the email subject to include release name and build number
             job.setdefault('extra', {}).update(
                 {
                    'notify': {
                        'email': {
                             'subject': subject,
                         }
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -373,37 +373,16 @@ task_description_schema = Schema({
 
         # os user groups for test task workers
         Optional('os-groups'): [basestring],
 
         # optional features
         Required('chain-of-trust'): bool,
         Optional('taskcluster-proxy'): bool,
     }, {
-        Required('implementation'): 'buildbot-bridge',
-
-        # see
-        # https://github.com/mozilla/buildbot-bridge/blob/master/bbb/schemas/payload.yml
-        Required('buildername'): basestring,
-        Required('sourcestamp'): {
-            'branch': basestring,
-            Optional('revision'): basestring,
-            Optional('repository'): basestring,
-            Optional('project'): basestring,
-        },
-        Required('properties'): {
-            'product': basestring,
-            Optional('build_number'): int,
-            Optional('release_promotion'): bool,
-            Optional('generate_bz2_blob'): bool,
-            Optional('tuxedo_server_url'): optionally_keyed_by('project', basestring),
-            Optional('release_eta'): basestring,
-            Extra: taskref_or_string,  # additional properties are allowed
-        },
-    }, {
         Required('implementation'): 'native-engine',
         Required('os'): Any('macosx', 'linux'),
 
         # the maximum time to run, in seconds
         Required('max-run-time'): int,
 
         # A link for an executable to download
         Optional('context'): basestring,
@@ -1202,39 +1181,16 @@ def build_macosx_engine_payload(config, 
     }
     if worker.get('reboot'):
         task_def['payload'] = worker['reboot']
 
     if task.get('needs-sccache'):
         raise Exception('needs-sccache not supported in native-engine')
 
 
-@payload_builder('buildbot-bridge')
-def build_buildbot_bridge_payload(config, task, task_def):
-    task['extra'].pop('treeherder', None)
-    worker = task['worker']
-
-    if worker['properties'].get('tuxedo_server_url'):
-        resolve_keyed_by(
-            worker, 'properties.tuxedo_server_url', worker['buildername'],
-            **config.params
-        )
-
-    task_def['payload'] = {
-        'buildername': worker['buildername'],
-        'sourcestamp': worker['sourcestamp'],
-        'properties': worker['properties'],
-    }
-    task_def.setdefault('scopes', [])
-    if worker['properties'].get('release_promotion'):
-        task_def['scopes'].append(
-            "project:releng:buildbot-bridge:builder-name:{}".format(worker['buildername'])
-        )
-
-
 transforms = TransformSequence()
 
 
 @transforms.add
 def set_defaults(config, tasks):
     for task in tasks:
         task.setdefault('shipping-phase', None)
         task.setdefault('shipping-product', None)
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -103,32 +103,16 @@ WINDOWS_WORKER_TYPES = {
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
       'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows10-64-qr': {
       'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
       'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
-    # These values don't really matter since BBB will be executing them
-    'windows8-64': {
-      'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
-      'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
-      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
-    },
-    'windows8-64-pgo': {
-      'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
-      'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
-      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
-    },
-    'windows8-64-nightly': {
-      'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
-      'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
-      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
-    },
 }
 
 # os x worker types keyed by test-platform
 MACOSX_WORKER_TYPES = {
     'macosx64': 'releng-hardware/gecko-t-osx-1010',
 }
 
 logger = logging.getLogger(__name__)
deleted file mode 100644
--- a/taskcluster/taskgraph/util/bbb_validation.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-# 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/.
-
-from __future__ import absolute_import, print_function, unicode_literals
-
-import json
-import logging
-import requests
-
-from mozbuild.util import memoize
-from redo import retry
-from requests import exceptions
-
-ALLTHETHINGS_URL = "https://secure.pub.build.mozilla.org/builddata/reports/allthethings.json.gz"
-
-logger = logging.getLogger(__name__)
-
-
-def fetch_all_the_things():
-    response = retry(requests.get, attempts=2, sleeptime=10,
-                     args=(ALLTHETHINGS_URL,),
-                     kwargs={'timeout': 60})
-    return response.content
-
-
-@memoize
-def valid_bbb_builders():
-    try:
-        allthethings = fetch_all_the_things()
-        builders = set(json.loads(allthethings).get('builders', {}).keys())
-        return builders
-
-    # In the event of request times out, requests will raise a TimeoutError.
-    except exceptions.Timeout:
-        logger.warning("Timeout fetching list of buildbot builders.")
-
-    # In the event of a network problem (e.g. DNS failure, refused connection, etc),
-    # requests will raise a ConnectionError.
-    except exceptions.ConnectionError:
-        logger.warning("Connection Error while fetching list of buildbot builders")
-
-    # We just print the error out as a debug message if we failed to catch the exception above
-    except exceptions.RequestException as error:
-        logger.warning(error)
-
-    # When we get invalid JSON (i.e. 500 error), it results in a ValueError
-    except ValueError as error:
-        logger.warning("Invalid JSON, possible server error: {}".format(error))
-
-    # None returned to treat as "All Builders Valid"
-    return None
--- a/taskcluster/taskgraph/util/schema.py
+++ b/taskcluster/taskgraph/util/schema.py
@@ -142,22 +142,16 @@ def resolve_keyed_by(item, field, item_n
 
 
 # Schemas for YAML files should use dashed identifiers by default.  If there are
 # components of the schema for which there is a good reason to use another format,
 # they can be whitelisted here.
 WHITELISTED_SCHEMA_IDENTIFIERS = [
     # upstream-artifacts are handed directly to scriptWorker, which expects interCaps
     lambda path: "[u'upstream-artifacts']" in path,
-    # bbb release promotion properties
-    lambda path: path.endswith("[u'build_number']"),
-    lambda path: path.endswith("[u'tuxedo_server_url']"),
-    lambda path: path.endswith("[u'release_promotion']"),
-    lambda path: path.endswith("[u'generate_bz2_blob']"),
-    lambda path: path.endswith("[u'release_eta']"),
 ]
 
 
 def check_schema(schema):
     identifier_re = re.compile('^[a-z][a-z0-9-]*$')
 
     def whitelisted(path):
         return any(f(path) for f in WHITELISTED_SCHEMA_IDENTIFIERS)
--- a/taskcluster/taskgraph/util/seta.py
+++ b/taskcluster/taskgraph/util/seta.py
@@ -44,27 +44,23 @@ class SETA(object):
 
         if len(task_tuple) == 0:
             return ''
         if len(task_tuple) != 3:
             return ' '.join(task_tuple)
 
         return 'test-%s/%s-%s' % (task_tuple[0], task_tuple[1], task_tuple[2])
 
-    def query_low_value_tasks(self, project, bbb=False):
-        # Request the set of low value tasks from the SETA service.  Low value tasks will be
-        # optimized out of the task graph.
+    def query_low_value_tasks(self, project):
+        # Request the set of low value tasks from the SETA service.  Low value
+        # tasks will be optimized out of the task graph.
         low_value_tasks = []
 
-        if not bbb:
-            # we want to get low priority taskcluster jobs
-            url = SETA_ENDPOINT % (project, 'taskcluster')
-        else:
-            # we want low priority buildbot jobs
-            url = SETA_ENDPOINT % (project, 'buildbot&priority=5')
+        # we want to get low priority taskcluster jobs
+        url = SETA_ENDPOINT % (project, 'taskcluster')
 
         # Try to fetch the SETA data twice, falling back to an empty list of low value tasks.
         # There are 10 seconds between each try.
         try:
             logger.debug("Retrieving low-value jobs list from SETA")
             response = retry(requests.get, attempts=2, sleeptime=10,
                              args=(url, ),
                              kwargs={'timeout': 60, 'headers': ''})
@@ -167,17 +163,17 @@ class SETA(object):
 
         # We just print the error out as a debug message if we failed to catch the exception above
         except exceptions.RequestException as error:
             logger.warning(error)
             self.failed_json_push_calls.append(prev_push_id)
 
         return min_between_pushes
 
-    def is_low_value_task(self, label, project, pushlog_id, push_date, bbb_task=False):
+    def is_low_value_task(self, label, project, pushlog_id, push_date):
         # marking a task as low_value means it will be optimized out by tc
         if project not in SETA_PROJECTS:
             return False
 
         schedule_all_every = PROJECT_SCHEDULE_ALL_EVERY_PUSHES.get(project, 5)
         # on every Nth push, want to run all tasks
         if int(pushlog_id) % schedule_all_every == 0:
             return False
@@ -185,24 +181,17 @@ class SETA(object):
         # Nth push, so time to call seta based on number of pushes; however
         # we also want to ensure we run all tasks at least once per N minutes
         if self.minutes_between_pushes(
                 project,
                 int(pushlog_id),
                 int(push_date)) >= PROJECT_SCHEDULE_ALL_EVERY_MINUTES.get(project, 60):
             return False
 
-        if not bbb_task:
-            # cache the low value tasks per project to avoid repeated SETA server queries
-            if project not in self.low_value_tasks:
-                self.low_value_tasks[project] = self.query_low_value_tasks(project)
-            return label in self.low_value_tasks[project]
-
-        # gecko decision task requesting if a bbb task is a low value task, so use bb jobs
-        # in this case, the label param sent in will be the buildbot buildername already
-        if project not in self.low_value_bb_tasks:
-            self.low_value_bb_tasks[project] = self.query_low_value_tasks(project, bbb=True)
-        return label in self.low_value_bb_tasks[project]
+        # cache the low value tasks per project to avoid repeated SETA server queries
+        if project not in self.low_value_tasks:
+            self.low_value_tasks[project] = self.query_low_value_tasks(project)
+        return label in self.low_value_tasks[project]
 
 
 # create a single instance of this class, and expose its `is_low_value_task`
 # bound method as a module-level function
 is_low_value_task = SETA().is_low_value_task
--- a/taskcluster/taskgraph/util/verify.py
+++ b/taskcluster/taskgraph/util/verify.py
@@ -6,17 +6,16 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 import logging
 import re
 import os
 import sys
 
 from .. import GECKO
-from taskgraph.util.bbb_validation import valid_bbb_builders
 
 logger = logging.getLogger(__name__)
 base_path = os.path.join(GECKO, 'taskcluster', 'docs')
 
 
 class VerificationSequence(object):
     """
     Container for a sequence of verifications over a TaskGraph. Each
@@ -138,58 +137,30 @@ def verify_dependency_tiers(task, taskgr
                                      .get('tier', sys.maxint)
     else:
         def printable_tier(tier):
             if tier == sys.maxint:
                 return 'unknown'
             return tier
 
         for task in taskgraph.tasks.itervalues():
-            # Buildbot bridge tasks cannot have tiers, so we cannot enforce
-            # this check for them
-            if task.task.get("workerType") == "buildbot-bridge":
-                continue
             tier = tiers[task.label]
             for d in task.dependencies.itervalues():
-                if taskgraph[d].task.get("workerType") in ("buildbot-bridge",
-                                                           "always-optimized"):
+                if taskgraph[d].task.get("workerType") == "always-optimized":
                     continue
                 if "dummy" in taskgraph[d].kind:
                     continue
                 if tier < tiers[d]:
                     raise Exception(
                         '{} (tier {}) cannot depend on {} (tier {})'
                         .format(task.label, printable_tier(tier),
                                 d, printable_tier(tiers[d])))
 
 
 @verifications.add('optimized_task_graph')
-def verify_bbb_builders_valid(task, taskgraph, scratch_pad):
-    """
-        This function ensures that any task which is run
-        in buildbot (via buildbot-bridge) is using a recognized buildername.
-
-        If you see an unexpected failure with a task due to this check, please
-        see the IRC Channel, #releng.
-    """
-    if task is None:
-        return
-    valid_builders = valid_bbb_builders()
-    if valid_builders is None:
-        return
-    if task.task.get('workerType') == 'buildbot-bridge':
-        buildername = task.task['payload']['buildername']
-        if buildername not in valid_builders:
-            logger.warning(
-                '{} uses an invalid buildbot buildername ("{}") '
-                ' - contact #releng for help'
-                .format(task.label, buildername))
-
-
-@verifications.add('optimized_task_graph')
 def verify_always_optimized(task, taskgraph, scratch_pad):
     """
         This function ensures that always-optimized tasks have been optimized.
     """
     if task is None:
         return
     if task.task.get('workerType') == 'always-optimized':
         raise Exception('Could not optimize the task {!r}'.format(task.label))
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -32,17 +32,16 @@ WORKER_TYPES = {
     'aws-provisioner-v1/gecko-t-linux-xlarge': ('docker-worker', 'linux'),
     'aws-provisioner-v1/gecko-t-win10-64': ('generic-worker', 'windows'),
     'aws-provisioner-v1/gecko-t-win10-64-gpu': ('generic-worker', 'windows'),
     'releng-hardware/gecko-t-win10-64-hw': ('generic-worker', 'windows'),
     'aws-provisioner-v1/gecko-t-win7-32': ('generic-worker', 'windows'),
     'aws-provisioner-v1/gecko-t-win7-32-gpu': ('generic-worker', 'windows'),
     'releng-hardware/gecko-t-win7-32-hw': ('generic-worker', 'windows'),
     'aws-provisioner-v1/taskcluster-generic': ('docker-worker', 'linux'),
-    'buildbot-bridge/buildbot-bridge': ('buildbot-bridge', None),
     'invalid/invalid': ('invalid', None),
     'invalid/always-optimized': ('always-optimized', None),
     'releng-hardware/gecko-t-linux-talos': ('native-engine', 'linux'),
     'scriptworker-prov-v1/balrog-dev': ('balrog', None),
     'scriptworker-prov-v1/balrogworker-v1': ('balrog', None),
     'scriptworker-prov-v1/beetmoverworker-v1': ('beetmover', None),
     'scriptworker-prov-v1/pushapk-v1': ('push-apk', None),
     "scriptworker-prov-v1/signing-linux-v1": ('scriptworker-signing', None),
--- a/testing/mochitest/runjunit.py
+++ b/testing/mochitest/runjunit.py
@@ -1,28 +1,27 @@
 # 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/.
 
 import argparse
-import json
 import os
 import posixpath
 import re
 import shutil
 import sys
 import tempfile
 import traceback
 
 import mozcrash
 import mozinfo
 import mozlog
 import moznetwork
 from mozdevice import ADBAndroid
-from mozprofile import Profile, Preferences, DEFAULT_PORTS
+from mozprofile import Profile, DEFAULT_PORTS
 from mozprofile.permissions import ServerLocations
 from runtests import MochitestDesktop, update_mozinfo
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 try:
     from mozbuild.base import (
         MozbuildObject,
@@ -94,43 +93,22 @@ class JUnitTestRunner(MochitestDesktop):
             self.options.certPath = os.path.join(build_obj.topsrcdir,
                                                  'build', 'pgo', 'certs')
 
     def build_profile(self):
         """
            Create a local profile with test prefs and proxy definitions and
            push it to the remote device.
         """
-        preferences = [
-            os.path.join(
-                here,
-                'profile_data',
-                'prefs_general.js')]
-
-        prefs = {}
-        for path in preferences:
-            prefs.update(Preferences.read_prefs(path))
-
-        interpolation = {
-            "server": "%s:%s" %
-            (self.options.webServer, self.options.httpPort)}
 
-        prefs = json.loads(json.dumps(prefs) % interpolation)
-        for pref in prefs:
-            prefs[pref] = Preferences.cast(prefs[pref])
+        self.profile = Profile(locations=self.locations, proxy=self.proxy(self.options))
+        self.options.profilePath = self.profile.profile
 
-        proxy = {'remote': self.options.webServer,
-                 'http': self.options.httpPort,
-                 'https': self.options.sslPort,
-                 'ws': self.options.sslPort
-                 }
-
-        self.profile = Profile(locations=self.locations, preferences=prefs,
-                               proxy=proxy)
-        self.options.profilePath = self.profile.profile
+        # Set preferences
+        self.merge_base_profiles(self.options)
 
         if self.fillCertificateDB(self.options):
             self.log.error("Certificate integration failed")
 
         self.device.mkdir(self.remote_profile, parents=True)
         self.device.push(self.profile.profile, self.remote_profile)
         self.log.debug("profile %s -> %s" %
                        (str(self.profile.profile), str(self.remote_profile)))
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -846,16 +846,17 @@ class MochitestDesktop(object):
     CHROME_PATH = "redirect.html"
 
     certdbNew = False
     sslTunnel = None
     DEFAULT_TIMEOUT = 60.0
     mediaDevices = None
 
     patternFiles = {}
+    base_profiles = ('common',)
 
     # XXX use automation.py for test name to avoid breaking legacy
     # TODO: replace this with 'runtests.py' or 'mochitest' or the like
     test_name = 'automation.py'
 
     def __init__(self, flavor, logger_options, quiet=False):
         update_mozinfo()
         self.flavor = flavor
@@ -904,25 +905,30 @@ class MochitestDesktop(object):
         self.result = {}
 
         self.start_script = os.path.join(here, 'start_desktop.js')
 
     def environment(self, **kwargs):
         kwargs['log'] = self.log
         return test_environment(**kwargs)
 
-    def extraPrefs(self, extraPrefs):
-        """interpolate extra preferences from option strings"""
+    def extraPrefs(self, prefs):
+        """Interpolate extra preferences from option strings"""
 
         try:
-            return dict(parseKeyValue(extraPrefs, context='--setpref='))
+            prefs = dict(parseKeyValue(prefs, context='--setpref='))
         except KeyValueParseError as e:
             print(str(e))
             sys.exit(1)
 
+        for pref, value in prefs.items():
+            value = Preferences.cast(value)
+            prefs[pref] = value
+        return prefs
+
     def getFullPath(self, path):
         " Get an absolute path relative to self.oldcwd."
         return os.path.normpath(
             os.path.join(
                 self.oldcwd,
                 os.path.expanduser(path)))
 
     def getLogFilePath(self, logFile):
@@ -1814,132 +1820,128 @@ toolbar#nav-bar {
             elif ext == ".client":
                 call([pk12util, "-i", os.path.join(options.certPath, item),
                       "-w", pwfilePath, "-d", certdbPath],
                      env=toolsEnv)
 
         os.unlink(pwfilePath)
         return 0
 
+    def proxy(self, options):
+        # proxy
+        # use SSL port for legacy compatibility; see
+        # - https://bugzilla.mozilla.org/show_bug.cgi?id=688667#c66
+        # - https://bugzilla.mozilla.org/show_bug.cgi?id=899221
+        # - https://github.com/mozilla/mozbase/commit/43f9510e3d58bfed32790c82a57edac5f928474d
+        #             'ws': str(self.webSocketPort)
+        return {
+            'remote': options.webServer,
+            'http': options.httpPort,
+            'https': options.sslPort,
+            'ws': options.sslPort,
+        }
+
+    def merge_base_profiles(self, options):
+        """Merge extra profile data from testing/profiles."""
+        profile_data_dir = os.path.join(SCRIPT_DIR, 'profile_data')
+
+        # If possible, read profile data from topsrcdir. This prevents us from
+        # requiring a re-build to pick up newly added extensions in the
+        # <profile>/extensions directory.
+        if build_obj:
+            path = os.path.join(build_obj.topsrcdir, 'testing', 'profiles')
+            if os.path.isdir(path):
+                profile_data_dir = path
+
+        # values to use when interpolating preferences
+        interpolation = {
+            "server": "%s:%s" % (options.webServer, options.httpPort),
+        }
+
+        for profile in self.base_profiles:
+            path = os.path.join(profile_data_dir, profile)
+            self.profile.merge(path, interpolation=interpolation)
+
     def buildProfile(self, options):
         """ create the profile and add optional chrome bits and files if requested """
-        if options.flavor == 'browser' and options.timeout:
-            options.extraPrefs.append(
-                "testing.browserTestHarness.timeout=%d" %
-                options.timeout)
-        # browser-chrome tests use a fairly short default timeout of 45 seconds;
-        # this is sometimes too short on asan and debug, where we expect reduced
-        # performance.
-        if (mozinfo.info["asan"] or mozinfo.info["debug"]) and \
-                options.flavor == 'browser' and options.timeout is None:
-            self.log.info("Increasing default timeout to 90 seconds")
-            options.extraPrefs.append("testing.browserTestHarness.timeout=90")
-
-        options.extraPrefs.append(
-            "browser.tabs.remote.autostart=%s" %
-            ('true' if options.e10s else 'false'))
-
-        options.extraPrefs.append(
-            "dom.ipc.tabs.nested.enabled=%s" %
-            ('true' if options.nested_oop else 'false'))
-
-        options.extraPrefs.append(
-            "idle.lastDailyNotification=%d" %
-            int(time.time()))
-
-        # Enable tracing output for detailed failures in case of
-        # failing connection attempts, and hangs (bug 1397201)
-        options.extraPrefs.append("marionette.log.level=%s" % "TRACE")
-
-        if getattr(self, 'testRootAbs', None):
-            options.extraPrefs.append(
-                "mochitest.testRoot=%s" %
-                self.testRootAbs)
-
         # get extensions to install
         extensions = self.getExtensionsToInstall(options)
 
-        # preferences
-        preferences = [
-            os.path.join(
-                SCRIPT_DIR,
-                'profile_data',
-                'prefs_general.js')]
-
-        prefs = {}
-        for path in preferences:
-            prefs.update(Preferences.read_prefs(path))
-
-        prefs.update(self.extraPrefs(options.extraPrefs))
-
-        # Bug 1262954: For windows XP + e10s disable acceleration
-        if platform.system() in ("Windows", "Microsoft") and \
-           '5.1' in platform.version() and options.e10s:
-            prefs['layers.acceleration.disabled'] = True
-
         # Whitelist the _tests directory (../..) so that TESTING_JS_MODULES work
         tests_dir = os.path.dirname(os.path.dirname(SCRIPT_DIR))
         sandbox_whitelist_paths = [tests_dir] + options.sandboxReadWhitelist
         if (platform.system() == "Linux" or
             platform.system() in ("Windows", "Microsoft")):
             # Trailing slashes are needed to indicate directories on Linux and Windows
             sandbox_whitelist_paths = map(lambda p: os.path.join(p, ""),
                                           sandbox_whitelist_paths)
 
-        # interpolate preferences
-        interpolation = {
-            "server": "%s:%s" %
-            (options.webServer, options.httpPort)}
-
-        prefs = json.loads(json.dumps(prefs) % interpolation)
-        for pref in prefs:
-            prefs[pref] = Preferences.cast(prefs[pref])
-        # TODO: make this less hacky
-        # https://bugzilla.mozilla.org/show_bug.cgi?id=913152
-
-        # proxy
-        # use SSL port for legacy compatibility; see
-        # - https://bugzilla.mozilla.org/show_bug.cgi?id=688667#c66
-        # - https://bugzilla.mozilla.org/show_bug.cgi?id=899221
-        # - https://github.com/mozilla/mozbase/commit/43f9510e3d58bfed32790c82a57edac5f928474d
-        #             'ws': str(self.webSocketPort)
-        proxy = {'remote': options.webServer,
-                 'http': options.httpPort,
-                 'https': options.sslPort,
-                 'ws': options.sslPort
-                 }
-
-        # See if we should use fake media devices.
-        if options.useTestMediaDevices:
-            prefs['media.audio_loopback_dev'] = self.mediaDevices['audio']
-            prefs['media.video_loopback_dev'] = self.mediaDevices['video']
-
-        # create a profile
+        # Create the profile
         self.profile = Profile(profile=options.profilePath,
                                addons=extensions,
                                locations=self.locations,
-                               preferences=prefs,
-                               proxy=proxy,
-                               whitelistpaths=sandbox_whitelist_paths
+                               proxy=self.proxy(options),
+                               whitelistpaths=sandbox_whitelist_paths,
                                )
 
         # Fix options.profilePath for legacy consumers.
         options.profilePath = self.profile.profile
 
         manifest = self.addChromeToProfile(options)
         self.copyExtraFilesToProfile(options)
 
         # create certificate database for the profile
         # TODO: this should really be upstreamed somewhere, maybe mozprofile
         certificateStatus = self.fillCertificateDB(options)
         if certificateStatus:
             self.log.error(
                 "TEST-UNEXPECTED-FAIL | runtests.py | Certificate integration failed")
             return None
 
+        # Set preferences in the following order (latter overrides former):
+        # 1) Preferences from base profile (e.g from testing/profiles)
+        # 2) Prefs hardcoded in this function
+        # 3) Prefs from --setpref
+
+        # Prefs from base profiles
+        self.merge_base_profiles(options)
+
+        # Hardcoded prefs (TODO move these into a base profile)
+        prefs = {
+            "browser.tabs.remote.autostart": options.e10s,
+            "dom.ipc.tabs.nested.enabled": options.nested_oop,
+            "idle.lastDailyNotification": int(time.time()),
+            # Enable tracing output for detailed failures in case of
+            # failing connection attempts, and hangs (bug 1397201)
+            "marionette.log.level": "TRACE",
+        }
+
+        if options.flavor == 'browser' and options.timeout:
+            prefs["testing.browserTestHarness.timeout"] = options.timeout
+
+        # browser-chrome tests use a fairly short default timeout of 45 seconds;
+        # this is sometimes too short on asan and debug, where we expect reduced
+        # performance.
+        if (mozinfo.info["asan"] or mozinfo.info["debug"]) and \
+                options.flavor == 'browser' and options.timeout is None:
+            self.log.info("Increasing default timeout to 90 seconds")
+            prefs["testing.browserTestHarness.timeout"] = 90
+
+        if getattr(self, 'testRootAbs', None):
+            prefs['mochitest.testRoot'] = self.testRootAbs
+
+        # See if we should use fake media devices.
+        if options.useTestMediaDevices:
+            prefs['media.audio_loopback_dev'] = self.mediaDevices['audio']
+            prefs['media.video_loopback_dev'] = self.mediaDevices['video']
+
+        self.profile.set_preferences(prefs)
+
+        # Extra prefs from --setpref
+        self.profile.set_preferences(self.extraPrefs(options.extraPrefs))
         return manifest
 
     def getGMPPluginPath(self, options):
         if options.gmp_path:
             return options.gmp_path
 
         gmp_parentdirs = [
             # For local builds, GMP plugins will be under dist/bin.
--- a/testing/mochitest/tests/python/python.ini
+++ b/testing/mochitest/tests/python/python.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 subsuite = mochitest
-sequential = true
 
 [test_basic_mochitest_plain.py]
+sequential = true
 [test_get_active_tests.py]
+[test_build_profile.py]
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/test_build_profile.py
@@ -0,0 +1,77 @@
+# 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/.
+
+from __future__ import print_function, unicode_literals
+
+import os
+from argparse import Namespace
+
+from mozprofile.prefs import Preferences
+from mozprofile import Profile
+from six import string_types
+
+import mozunit
+import pytest
+from conftest import setup_args
+
+
+@pytest.fixture
+def build_profile(monkeypatch, setup_test_harness, parser):
+    setup_test_harness(*setup_args)
+    runtests = pytest.importorskip('runtests')
+    md = runtests.MochitestDesktop('plain', {'log_tbpl': '-'})
+    monkeypatch.setattr(md, 'fillCertificateDB', lambda *args, **kwargs: None)
+
+    options = parser.parse_args([])
+    options = vars(options)
+
+    def inner(**kwargs):
+        opts = options.copy()
+        opts.update(kwargs)
+
+        return md, md.buildProfile(Namespace(**opts))
+
+    return inner
+
+
+@pytest.fixture
+def profile_data_dir(build_obj):
+    return os.path.join(build_obj.topsrcdir, 'testing', 'profiles')
+
+
+def test_common_prefs_are_all_set(build_profile, profile_data_dir):
+    # We set e10s=False here because MochitestDesktop.buildProfile overwrites
+    # the value defined in the base profile.
+    # TODO stop setting browser.tabs.remote.autostart in the base profile
+    md, result = build_profile(e10s=False)
+
+    # build the expected prefs
+    expected_prefs = {}
+    for profile in md.base_profiles:
+        for name in Profile.preference_file_names:
+            path = os.path.join(profile_data_dir, profile, name)
+            if os.path.isfile(path):
+                expected_prefs.update(Preferences.read_prefs(path))
+
+    # read the actual prefs
+    actual_prefs = {}
+    for name in Profile.preference_file_names:
+        path = os.path.join(md.profile.profile, name)
+        if os.path.isfile(path):
+            actual_prefs.update(Preferences.read_prefs(path))
+
+    # keep this in sync with the values in MochitestDesktop.merge_base_profiles
+    interpolation = {
+        'server': '127.0.0.1:8888',
+    }
+    for k, v in expected_prefs.items():
+        if isinstance(v, string_types):
+            v = v.format(**interpolation)
+
+        assert k in actual_prefs
+        assert k and actual_prefs[k] == v
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/testing/mozbase/mozprofile/mozprofile/profile.py
+++ b/testing/mozbase/mozprofile/mozprofile/profile.py
@@ -5,17 +5,17 @@
 from __future__ import absolute_import
 
 import json
 import os
 import platform
 import tempfile
 import time
 import uuid
-from abc import ABCMeta, abstractmethod
+from abc import ABCMeta, abstractmethod, abstractproperty
 from shutil import copytree
 
 import mozfile
 from six import string_types
 
 from .addons import AddonManager
 from .permissions import Permissions
 from .prefs import Preferences
@@ -27,17 +27,28 @@ from .prefs import Preferences
            'ThunderbirdProfile',
            'create_profile']
 
 
 class BaseProfile(object):
     __metaclass__ = ABCMeta
 
     def __init__(self, profile=None, addons=None, preferences=None, restore=True):
-        self._addons = addons
+        """Create a new Profile.
+
+        All arguments are optional.
+
+        :param profile: Path to a profile. If not specified, a new profile
+                        directory will be created.
+        :param addons: List of paths to addons which should be installed in the profile.
+        :param preferences: Dict of preferences to set in the profile.
+        :param restore: Whether or not to clean up any modifications made to this profile
+                        (default True).
+        """
+        self._addons = addons or []
 
         # Prepare additional preferences
         if preferences:
             if isinstance(preferences, dict):
                 # unordered
                 preferences = preferences.items()
 
             # sanity check
@@ -78,16 +89,50 @@ class BaseProfile(object):
 
     def reset(self):
         """
         reset the profile to the beginning state
         """
         self.cleanup()
         self._reset()
 
+    @abstractmethod
+    def set_preferences(self, preferences, filename='user.js'):
+        pass
+
+    @abstractproperty
+    def preference_file_names(self):
+        """A tuple of file basenames expected to contain preferences."""
+
+    def merge(self, other, interpolation=None):
+        """Merges another profile into this one.
+
+        This will handle pref files matching the profile's
+        `preference_file_names` property, and any addons in the
+        other/extensions directory.
+        """
+        for basename in os.listdir(other):
+            if basename not in self.preference_file_names:
+                continue
+
+            path = os.path.join(other, basename)
+            try:
+                prefs = Preferences.read_json(path)
+            except ValueError:
+                prefs = Preferences.read_prefs(path, interpolation=interpolation)
+            self.set_preferences(prefs, filename=basename)
+
+        extension_dir = os.path.join(other, 'extensions')
+        for basename in os.listdir(extension_dir):
+            path = os.path.join(extension_dir, basename)
+
+            if self.addons.is_addon(path):
+                self._addons.append(path)
+                self.addons.install(path)
+
     @classmethod
     def clone(cls, path_from, path_to=None, ignore=None, **kwargs):
         """Instantiate a temporary profile via cloning
         - path: path of the basis to clone
         - ignore: callable passed to shutil.copytree
         - kwargs: arguments to the profile constructor
         """
         if not path_to:
@@ -123,31 +168,32 @@ class Profile(BaseProfile):
     can ensure this method is called (even in the case of exception) by using
     the profile as a context manager: ::
 
       with Profile() as profile:
           # do things with the profile
           pass
       # profile.cleanup() has been called here
     """
+    preference_file_names = ('user.js', 'prefs.js')
 
     def __init__(self, profile=None, addons=None, preferences=None, locations=None,
-                 proxy=None, restore=True, whitelistpaths=None):
+                 proxy=None, restore=True, whitelistpaths=None, **kwargs):
         """
         :param profile: Path to the profile
         :param addons: String of one or list of addons to install
         :param preferences: Dictionary or class of preferences
         :param locations: ServerLocations object
         :param proxy: Setup a proxy
         :param restore: Flag for removing all custom settings during cleanup
         :param whitelistpaths: List of paths to pass to Firefox to allow read
             access to from the content process sandbox.
         """
         super(Profile, self).__init__(
-            profile=profile, addons=addons, preferences=preferences, restore=restore)
+            profile=profile, addons=addons, preferences=preferences, restore=restore, **kwargs)
 
         self._locations = locations
         self._proxy = proxy
         self._whitelistpaths = whitelistpaths
 
         # Initialize all class members
         self._reset()
 
@@ -221,37 +267,32 @@ class Profile(BaseProfile):
             while True:
                 if not self.pop_preferences(filename):
                     break
 
     # methods for preferences
 
     def set_preferences(self, preferences, filename='user.js'):
         """Adds preferences dict to profile preferences"""
-
-        # append to the file
         prefs_file = os.path.join(self.profile, filename)
-        f = open(prefs_file, 'a')
-
-        if preferences:
+        with open(prefs_file, 'a') as f:
+            if not preferences:
+                return
 
             # note what files we've touched
             self.written_prefs.add(filename)
 
             # opening delimeter
             f.write('\n%s\n' % self.delimeters[0])
 
-            # write the preferences
             Preferences.write(f, preferences)
 
             # closing delimeter
             f.write('%s\n' % self.delimeters[1])
 
-        f.close()
-
     def set_persistent_preferences(self, preferences):
         """
         Adds preferences dict to profile preferences and save them during a
         profile reset
         """
 
         # this is a dict sometimes, convert
         if isinstance(preferences, dict):
@@ -440,49 +481,61 @@ class ThunderbirdProfile(Profile):
                    'browser.warnOnQuit': False,
                    'browser.sessionstore.resume_from_crash': False,
                    # prevents the 'new e-mail address' wizard on new profile
                    'mail.provider.enabled': False,
                    }
 
 
 class ChromeProfile(BaseProfile):
+    preference_file_names = ('Preferences',)
+
     class AddonManager(list):
         def install(self, addons):
             if isinstance(addons, string_types):
                 addons = [addons]
             self.extend(addons)
 
+        @classmethod
+        def is_addon(self, addon):
+            # TODO Implement this properly
+            return os.path.exists(addon)
+
     def __init__(self, **kwargs):
         super(ChromeProfile, self).__init__(**kwargs)
 
         if self.create_new:
             self.profile = os.path.join(self.profile, 'Default')
         self._reset()
 
     def _reset(self):
         if not os.path.isdir(self.profile):
             os.makedirs(self.profile)
 
         if self._preferences:
-            pref_file = os.path.join(self.profile, 'Preferences')
-
-            prefs = {}
-            if os.path.isfile(pref_file):
-                with open(pref_file, 'r') as fh:
-                    prefs.update(json.load(fh))
-
-            prefs.update(self._preferences)
-            with open(pref_file, 'w') as fh:
-                json.dump(prefs, fh)
+            self.set_preferences(self._preferences)
 
         self.addons = self.AddonManager()
         if self._addons:
             self.addons.install(self._addons)
 
+    def set_preferences(self, preferences, filename='Preferences', **values):
+        pref_file = os.path.join(self.profile, filename)
+
+        prefs = {}
+        if os.path.isfile(pref_file):
+            with open(pref_file, 'r') as fh:
+                prefs.update(json.load(fh))
+
+        prefs.update(preferences)
+        with open(pref_file, 'w') as fh:
+            prefstr = json.dumps(prefs)
+            prefstr % values  # interpolate prefs with values
+            fh.write(prefstr)
+
 
 profile_class = {
     'chrome': ChromeProfile,
     'firefox': FirefoxProfile,
     'thunderbird': ThunderbirdProfile,
 }
 
 
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozprofile/tests/files/dummy-profile/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  globals: {
+    user_pref: true,
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozprofile/tests/files/dummy-profile/Preferences
@@ -0,0 +1,1 @@
+{"Preferences": 1}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..26f28f099d24bec509e3abd991ff453c667543d6
GIT binary patch
literal 530
zc$^FHW@Zs#U|`^2a4J``_nh^LHI0#hVG9!j12=;VLuOuaNn%cpUQtR~Xb2|*^KFSV
zzmq^*TEWf0$nq7a60Es&lD~hmfyA-%pER}Nt}NMdpu{QSs!RE$=I*NZA5zU0vU;og
zJT9L;^<1u7{@c%g=IyJ$^k&m!)hhP89bUeF3>N%T{k-YsS&^@8S!(}Q8<u)`+}`FW
z%|115f`O-yld_O&#D0+-k(!6C*UVC`Ke0sNd(x&~0;wl+%@&ooIIL@SxBJYuNjjrQ
zH|z3r_nJ7}vq$2yrtb353Q4YrOgcGLV}ar8GmdOVqWWjPl=EydTfKU_s(x$a+i9mx
zI9T56J&_?0m0n!F?UB%p<o^o0-tw&2+S>Q_wz}B%eaXeVcS2^}<v)7i@K#r|O^f5S
zIZfjaTz=EHGxg8)r+Tk0rG+wgugUJe7iE7$!nd_;(JO&t_8Ql@4X&5QcFmDCzkDxg
z?S`AT)Srl(EITUkT<~c{iJew~(ej4FGmhIdrpUzi>?_-5uCg@Xd|ql{f!Lg=1ot~l
zdv0g&-QwD2_-Xa##1r;yH`E0`8D0AC{j8Qbz?+dtju}^ENicu_kjv1}2x6f`9V;a2
W(4sBCo0ScsiIE`?NUsLzW&i+p&C!|w
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozprofile/tests/files/dummy-profile/prefs.js
@@ -0,0 +1,1 @@
+user_pref("prefs.js", 1);
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozprofile/tests/files/dummy-profile/user.js
@@ -0,0 +1,1 @@
+user_pref("user.js", 1);
--- a/testing/mozbase/mozprofile/tests/test_profile.py
+++ b/testing/mozbase/mozprofile/tests/test_profile.py
@@ -6,25 +6,28 @@
 
 from __future__ import absolute_import
 
 import os
 
 import mozunit
 import pytest
 
+from mozprofile.prefs import Preferences
 from mozprofile import (
     BaseProfile,
     Profile,
     ChromeProfile,
     FirefoxProfile,
     ThunderbirdProfile,
     create_profile,
 )
 
+here = os.path.abspath(os.path.dirname(__file__))
+
 
 def test_with_profile_should_cleanup():
     with Profile() as profile:
         assert os.path.exists(profile.profile)
 
     # profile is cleaned
     assert not os.path.exists(profile.profile)
 
@@ -54,10 +57,42 @@ def test_create_profile(tmpdir, app, cls
         return
 
     profile = create_profile(app, profile=path)
     assert isinstance(profile, BaseProfile)
     assert profile.__class__ == cls
     assert profile.profile == path
 
 
+@pytest.mark.parametrize('cls', [
+    Profile,
+    ChromeProfile,
+])
+def test_merge_profile(cls):
+    profile = cls(preferences={'foo': 'bar'})
+    assert profile._addons == []
+    assert os.path.isfile(os.path.join(profile.profile, profile.preference_file_names[0]))
+
+    other_profile = os.path.join(here, 'files', 'dummy-profile')
+    profile.merge(other_profile)
+
+    # make sure to add a pref file for each preference_file_names in the dummy-profile
+    prefs = {}
+    for name in profile.preference_file_names:
+        path = os.path.join(profile.profile, name)
+        assert os.path.isfile(path)
+
+        try:
+            prefs.update(Preferences.read_json(path))
+        except ValueError:
+            prefs.update(Preferences.read_prefs(path))
+
+    assert 'foo' in prefs
+    assert len(prefs) == len(profile.preference_file_names) + 1
+    assert all(name in prefs for name in profile.preference_file_names)
+
+    assert len(profile._addons) == 1
+    assert profile._addons[0].endswith('empty.xpi')
+    assert os.path.exists(profile._addons[0])
+
+
 if __name__ == '__main__':
     mozunit.main()
new file mode 100644
--- /dev/null
+++ b/testing/profiles/common/extensions/README.txt
@@ -0,0 +1,2 @@
+Dropping extensions here will get them installed in all test harnesses
+that make use of this profile.
rename from testing/profiles/prefs_general.js
rename to testing/profiles/common/user.js
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/common/user.js
@@ -54,19 +54,19 @@ user_pref("media.volume_scale", "0.01");
 user_pref("media.test.dumpDebugInfo", true);
 user_pref("media.dormant-on-pause-timeout-ms", 0); // Enter dormant immediately without waiting for timeout.
 user_pref("media.suspend-bkgnd-video.enabled", false);
 user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
 user_pref("app.update.staging.enabled", false);
 user_pref("app.update.url.android", "");
 // Make sure GMPInstallManager won't hit the network.
-user_pref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml");
+user_pref("media.gmp-manager.url.override", "http://{server}/dummy-gmp-manager.xml");
 user_pref("media.gmp-manager.updateEnabled", false);
-user_pref("media.hls.server.url", "http://%(server)s/tests/dom/media/test/hls");
+user_pref("media.hls.server.url", "http://{server}/tests/dom/media/test/hls");
 user_pref("dom.w3c_touch_events.enabled", 1);
 user_pref("layout.accessiblecaret.enabled_on_touch", false);
 user_pref("dom.webcomponents.shadowdom.enabled", false);
 user_pref("dom.webcomponents.customelements.enabled", true);
 // Existing tests assume there is no font size inflation.
 user_pref("font.size.inflation.emPerLine", 0);
 user_pref("font.size.inflation.minTwips", 0);
 // Disable the caret blinking so we get stable snapshot
@@ -84,85 +84,85 @@ user_pref("extensions.autoDisableScopes"
 user_pref("extensions.getAddons.cache.enabled", false);
 // Disable intalling any distribution add-ons
 user_pref("extensions.installDistroAddons", false);
 // XPI extensions are required for test harnesses to load
 user_pref("extensions.defaultProviders.enabled", true);
 user_pref("xpinstall.signatures.required", false);
 user_pref("extensions.legacy.enabled", true);
 
-user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
+user_pref("geo.wifi.uri", "http://{server}/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
 user_pref("geo.wifi.timeToWaitBeforeSending", 2000);
 user_pref("geo.wifi.scan", false);
 user_pref("geo.wifi.logging.enabled", true);
 
 // Prevent connection to the push server for tests.
 user_pref("dom.push.connection.enabled", false);
 
 // Point the url-classifier to the local testing server for fast failures
-user_pref("browser.safebrowsing.downloads.remote.url", "http://%(server)s/safebrowsing-dummy/update");
-user_pref("browser.safebrowsing.provider.google.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
-user_pref("browser.safebrowsing.provider.google.updateURL", "http://%(server)s/safebrowsing-dummy/update");
-user_pref("browser.safebrowsing.provider.google4.gethashURL", "http://%(server)s/safebrowsing4-dummy/gethash");
-user_pref("browser.safebrowsing.provider.google4.updateURL", "http://%(server)s/safebrowsing4-dummy/update");
-user_pref("browser.safebrowsing.provider.mozilla.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
-user_pref("browser.safebrowsing.provider.mozilla.updateURL", "http://%(server)s/safebrowsing-dummy/update");
-user_pref("privacy.trackingprotection.introURL", "http://%(server)s/trackingprotection/tour");
+user_pref("browser.safebrowsing.downloads.remote.url", "http://{server}/safebrowsing-dummy/update");
+user_pref("browser.safebrowsing.provider.google.gethashURL", "http://{server}/safebrowsing-dummy/gethash");
+user_pref("browser.safebrowsing.provider.google.updateURL", "http://{server}/safebrowsing-dummy/update");
+user_pref("browser.safebrowsing.provider.google4.gethashURL", "http://{server}/safebrowsing4-dummy/gethash");
+user_pref("browser.safebrowsing.provider.google4.updateURL", "http://{server}/safebrowsing4-dummy/update");
+user_pref("browser.safebrowsing.provider.mozilla.gethashURL", "http://{server}/safebrowsing-dummy/gethash");
+user_pref("browser.safebrowsing.provider.mozilla.updateURL", "http://{server}/safebrowsing-dummy/update");
+user_pref("privacy.trackingprotection.introURL", "http://{server}/trackingprotection/tour");
 // Point update checks to the local testing server for fast failures
-user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
-user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL");
-user_pref("extensions.blocklist.detailsURL", "http://%(server)s/extensions-dummy/blocklistDetailsURL");
-user_pref("extensions.blocklist.itemURL", "http://%(server)s/extensions-dummy/blocklistItemURL");
-user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
-user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
-user_pref("extensions.systemAddon.update.url", "http://%(server)s/dummy-system-addons.xml");
+user_pref("extensions.update.url", "http://{server}/extensions-dummy/updateURL");
+user_pref("extensions.update.background.url", "http://{server}/extensions-dummy/updateBackgroundURL");
+user_pref("extensions.blocklist.detailsURL", "http://{server}/extensions-dummy/blocklistDetailsURL");
+user_pref("extensions.blocklist.itemURL", "http://{server}/extensions-dummy/blocklistItemURL");
+user_pref("extensions.blocklist.url", "http://{server}/extensions-dummy/blocklistURL");
+user_pref("extensions.hotfix.url", "http://{server}/extensions-dummy/hotfixURL");
+user_pref("extensions.systemAddon.update.url", "http://{server}/dummy-system-addons.xml");
 // Turn off extension updates so they don't bother tests
 user_pref("extensions.update.enabled", false);
 // Make sure opening about:addons won't hit the network
-user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
+user_pref("extensions.webservice.discoverURL", "http://{server}/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
-user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
-user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
-user_pref("extensions.getAddons.search.browseURL", "http://%(server)s/extensions-dummy/repositoryBrowseURL");
+user_pref("extensions.getAddons.get.url", "http://{server}/extensions-dummy/repositoryGetURL");
+user_pref("extensions.getAddons.getWithPerformance.url", "http://{server}/extensions-dummy/repositoryGetWithPerformanceURL");
+user_pref("extensions.getAddons.search.browseURL", "http://{server}/extensions-dummy/repositoryBrowseURL");
 // Ensure blocklist updates don't hit the network
-user_pref("services.settings.server", "http://%(server)s/dummy-kinto/v1");
+user_pref("services.settings.server", "http://{server}/dummy-kinto/v1");
 // Make sure SNTP requests don't hit the network
-user_pref("network.sntp.pools", "%(server)s");
+user_pref("network.sntp.pools", "{server}");
 // We know the SNTP request will fail, since localhost isn't listening on
 // port 135. The default number of retries (10) is excessive, but retrying
 // at least once will mean that codepath is still tested in automation.
 user_pref("network.sntp.maxRetryCount", 1);
 
 // Make sure the notification permission migration test doesn't hit the network.
-user_pref("app.support.baseURL", "http://%(server)s/support-dummy/");
+user_pref("app.support.baseURL", "http://{server}/support-dummy/");
 
 // Existing tests don't wait for the notification button security delay
 user_pref("security.notification_enable_delay", 0);
 
 // Make enablePrivilege continue to work for test code. :-(
 user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", true);
 
 // In the default configuration, we bypass XBL scopes (a security feature) for
 // domains whitelisted for remote XUL, so that intranet apps and such continue
 // to work without major rewrites. However, we also use the whitelist mechanism
 // to run our XBL tests in automation, in which case we really want to be testing
 // the configuration that we ship to users without special whitelisting. So we
 // use an additional pref here to allow automation to use the "normal" behavior.
 user_pref("dom.use_xbl_scopes_for_remote_xul", true);
 
-user_pref("captivedetect.canonicalURL", "http://%(server)s/captive-detect/success.txt");
+user_pref("captivedetect.canonicalURL", "http://{server}/captive-detect/success.txt");
 
 // We do not wish to display datareporting policy notifications as it might
 // cause other tests to fail. Tests that wish to test the notification functionality
 // should explicitly disable this pref.
 user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
 
 // Point Firefox Health Report at a local server. We don't care if it actually
 // works. It just can't hit the default production endpoint.
-user_pref("datareporting.healthreport.documentServerURI", "http://%(server)s/healthreport/");
+user_pref("datareporting.healthreport.documentServerURI", "http://{server}/healthreport/");
 
 // Make sure CSS error reporting is enabled for tests
 user_pref("layout.css.report_errors", true);
 
 // Enable CSS Grid 'subgrid' feature for testing
 user_pref("layout.css.grid-template-subgrid-value.enabled", true);
 
 // Enable CSS 'contain' for testing
@@ -208,34 +208,34 @@ user_pref("browser.webapps.testing", tru
 
 // Disable android snippets
 user_pref("browser.snippets.enabled", false);
 user_pref("browser.snippets.syncPromo.enabled", false);
 user_pref("browser.snippets.firstrunHomepage.enabled", false);
 
 // Disable useragent updates.
 user_pref("general.useragent.updates.enabled", false);
-user_pref("general.useragent.updates.url", "https://example.com/0/%%APP_ID%%");
+user_pref("general.useragent.updates.url", "https://example.com/0/%APP_ID%");
 
 // Disable webapp updates.  Yes, it is supposed to be an integer.
 user_pref("browser.webapps.checkForUpdates", 0);
 
 user_pref("dom.presentation.testing.simulate-receiver", false);
 
 // Don't connect to Yahoo! for RSS feed tests.
 // en-US only uses .types.0.uri, but set all of them just to be sure.
-user_pref("browser.contentHandlers.types.0.uri", "http://test1.example.org/rss?url=%%s");
-user_pref("browser.contentHandlers.types.1.uri", "http://test1.example.org/rss?url=%%s");
-user_pref("browser.contentHandlers.types.2.uri", "http://test1.example.org/rss?url=%%s");
-user_pref("browser.contentHandlers.types.3.uri", "http://test1.example.org/rss?url=%%s");
-user_pref("browser.contentHandlers.types.4.uri", "http://test1.example.org/rss?url=%%s");
-user_pref("browser.contentHandlers.types.5.uri", "http://test1.example.org/rss?url=%%s");
+user_pref("browser.contentHandlers.types.0.uri", "http://test1.example.org/rss?url=%s");
+user_pref("browser.contentHandlers.types.1.uri", "http://test1.example.org/rss?url=%s");
+user_pref("browser.contentHandlers.types.2.uri", "http://test1.example.org/rss?url=%s");
+user_pref("browser.contentHandlers.types.3.uri", "http://test1.example.org/rss?url=%s");
+user_pref("browser.contentHandlers.types.4.uri", "http://test1.example.org/rss?url=%s");
+user_pref("browser.contentHandlers.types.5.uri", "http://test1.example.org/rss?url=%s");
 
 // We want to collect telemetry, but we don't want to send in the results.
-user_pref("toolkit.telemetry.server", "https://%(server)s/telemetry-dummy/");
+user_pref("toolkit.telemetry.server", "https://{server}/telemetry-dummy/");
 user_pref("datareporting.healthreport.uploadEnabled", false);
 // Don't send 'new-profile' ping on new profiles during tests, otherwise the testing framework
 // might wait on the pingsender to finish and slow down tests.
 user_pref("toolkit.telemetry.newProfilePing.enabled", false);
 // Don't send 'bhr' ping during tests, otherwise the testing framework might
 // wait on the pingsender to finish and slow down tests.
 user_pref("toolkit.telemetry.bhrPing.enabled", false);
 // Don't send the 'shutdown' ping using the pingsender on the first session using
@@ -251,51 +251,51 @@ user_pref("toolkit.telemetry.firstShutdo
 // A couple of preferences with default values to test that telemetry preference
 // watching is working.
 user_pref("toolkit.telemetry.test.pref1", true);
 user_pref("toolkit.telemetry.test.pref2", false);
 
 // We don't want to hit the real Firefox Accounts server for tests.  We don't
 // actually need a functioning FxA server, so just set it to something that
 // resolves and accepts requests, even if they all fail.
-user_pref("identity.fxaccounts.auth.uri", "https://%(server)s/fxa-dummy/");
+user_pref("identity.fxaccounts.auth.uri", "https://{server}/fxa-dummy/");
 
 // Ditto for all the FxA content root URI.
-user_pref("identity.fxaccounts.remote.root", "https://%(server)s/");
+user_pref("identity.fxaccounts.remote.root", "https://{server}/");
 
 // Increase the APZ content response timeout in tests to 1 minute.
 // This is to accommodate the fact that test environments tends to be slower
 // than production environments (with the b2g emulator being the slowest of them
 // all), resulting in the production timeout value sometimes being exceeded
 // and causing false-positive test failures. See bug 1176798, bug 1177018,
 // bug 1210465.
 user_pref("apz.content_response_timeout", 60000);
 
 // Make sure SSL Error reports don't hit the network
 user_pref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?succeed");
 
 // Make sure Translation won't hit the network.
-user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
-user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
-user_pref("browser.translation.yandex.translateURLOverride", "http://%(server)s/browser/browser/components/translation/test/yandex.sjs");
+user_pref("browser.translation.bing.authURL", "http://{server}/browser/browser/components/translation/test/bing.sjs");
+user_pref("browser.translation.bing.translateArrayURL", "http://{server}/browser/browser/components/translation/test/bing.sjs");
+user_pref("browser.translation.yandex.translateURLOverride", "http://{server}/browser/browser/components/translation/test/yandex.sjs");
 user_pref("browser.translation.engine", "bing");
 
 // Make sure we don't try to load snippets from the network.
 user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
 
 // Use an empty list of sites to avoid fetching
 user_pref("browser.newtabpage.activity-stream.default.sites", "");
 user_pref("browser.newtabpage.activity-stream.telemetry", false);
 user_pref("browser.newtabpage.activity-stream.tippyTop.service.endpoint", "");
 user_pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);
 user_pref("browser.newtabpage.activity-stream.feeds.snippets", false);
 
 // Ensure UITour won't hit the network
-user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
-user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
+user_pref("browser.uitour.pinnedTabUrl", "http://{server}/uitour-dummy/pinnedTab");
+user_pref("browser.uitour.url", "http://{server}/uitour-dummy/tour");
 
 // Tell the search service we are running in the US.  This also has the desired
 // side-effect of preventing our geoip lookup.
 user_pref("browser.search.isUS", true);
 user_pref("browser.search.countryCode", "US");
 // This will prevent HTTP requests for region defaults.
 user_pref("browser.search.geoSpecificDefaults", false);
 
--- a/testing/profiles/moz.build
+++ b/testing/profiles/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-mochitest_profile_files = [
-    'prefs_general.js',
+profiles = [
+    'common/*',
 ]
 
-TEST_HARNESS_FILES.testing.mochitest.profile_data += mochitest_profile_files
-TEST_HARNESS_FILES['web-platform'].prefs += mochitest_profile_files
+TEST_HARNESS_FILES.testing.mochitest.profile_data += profiles
+TEST_HARNESS_FILES['web-platform'].prefs += ['common/user.js']
 
 with Files("**"):
     BUG_COMPONENT = ("Testing", "Mochitest")
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -238,17 +238,17 @@ ifdef STRIP_COMPILED_TESTS
 else
 	cp -RL $(DIST)/bin/jsapi-tests$(BIN_SUFFIX) $(PKG_STAGE)/cppunittest
 endif
 
 stage-steeplechase: make-stage-dir
 	$(NSINSTALL) -D $(PKG_STAGE)/steeplechase/
 	cp -RL $(DEPTH)/_tests/steeplechase $(PKG_STAGE)/steeplechase/tests
 	cp -RL $(DIST)/xpi-stage/specialpowers $(PKG_STAGE)/steeplechase
-	cp -RL $(topsrcdir)/testing/profiles/prefs_general.js $(PKG_STAGE)/steeplechase
+	cp -RL $(topsrcdir)/testing/profiles/common/user.js $(PKG_STAGE)/steeplechase/prefs_general.js
 
 TEST_EXTENSIONS := \
     specialpowers@mozilla.org.xpi \
 	$(NULL)
 
 stage-extensions: make-stage-dir
 	$(NSINSTALL) -D $(PKG_STAGE)/extensions/
 	@$(foreach ext,$(TEST_EXTENSIONS), cp -RL $(DIST)/xpi-stage/$(ext) $(PKG_STAGE)/extensions;)
new file mode 100644
--- /dev/null
+++ b/testing/tps/mach_commands.py
@@ -0,0 +1,32 @@
+# 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/.
+
+from __future__ import absolute_import, print_function
+import os
+
+from mach.decorators import Command, CommandArgument, CommandProvider
+from mozbuild.base import MachCommandBase
+from mozpack.copier import Jarrer
+from mozpack.files import FileFinder
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+    """TPS tests for Sync."""
+
+    @Command('tps-build', category='testing', description='Build TPS add-on.')
+    @CommandArgument('--dest', default=None, help='Where to write add-on.')
+    def build(self, dest):
+        src = os.path.join(self.topsrcdir, 'services', 'sync', 'tps', 'extensions', 'tps')
+        dest = os.path.join(dest or os.path.join(self.topobjdir, 'services', 'sync'), 'tps.xpi')
+
+        if not os.path.exists(os.path.dirname(dest)):
+            os.makedirs(os.path.dirname(dest))
+
+        jarrer = Jarrer(optimize=False)
+        for p, f in FileFinder(src).find('*'):
+            jarrer.add(p, f)
+        jarrer.copy(dest)
+
+        print('Built TPS add-on as %s' % dest)
--- a/testing/web-platform/tests/tools/wpt/browser.py
+++ b/testing/web-platform/tests/tools/wpt/browser.py
@@ -200,28 +200,28 @@ class Firefox(Browser):
             if channel == "beta":
                 tag = "FIREFOX_%s_BETA" % version.split(".", 1)[0]
             else:
                 # Always use tip as the tag for nightly; this isn't quite right
                 # but to do better we need the actual build revision, which we
                 # can get if we have an application.ini file
                 tag = "tip"
 
-        return "%s/raw-file/%s/testing/profiles/prefs_general.js" % (repo, tag)
+        return "%s/raw-file/%s/testing/profiles/common/user.js" % (repo, tag)
 
     def install_prefs(self, binary, dest=None):
         version, channel = self.get_version_number(binary)
 
         if dest is None:
             dest = os.pwd
 
-        dest = os.path.join(dest, "profiles")
+        dest = os.path.join(dest, "profiles", "common")
         if not os.path.exists(dest):
             os.makedirs(dest)
-        prefs_file = os.path.join(dest, "prefs_general.js")
+        prefs_file = os.path.join(dest, "user.js")
         cache_file = os.path.join(dest,
                                   "%s-%s.cache" % (version, channel)
                                   if channel != "nightly"
                                   else "nightly.cache")
 
         have_cache = False
         if os.path.exists(cache_file):
             if channel != "nightly":
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -240,17 +240,17 @@ class FirefoxBrowser(Browser):
         self.logger.debug("Starting Firefox")
 
         self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
         self.logger.debug("Firefox Started")
 
     def load_prefs(self):
         prefs = Preferences()
 
-        prefs_path = os.path.join(self.prefs_root, "prefs_general.js")
+        prefs_path = os.path.join(self.prefs_root, "user.js")
         if os.path.exists(prefs_path):
             prefs.add(Preferences.read_prefs(prefs_path))
         else:
             self.logger.warning("Failed to find base prefs file in %s" % prefs_path)
 
         # Add any custom preferences
         prefs.add(self.extra_prefs, cast=True)
 
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
@@ -1263,23 +1263,56 @@ nsTypeAheadFind::IsRangeVisible(nsIPresS
   // We don't use the more accurate AccGetBounds, because that is
   // more expensive and the STATE_OFFSCREEN flag that this is used
   // for only needs to be a rough indicator
   nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport;
 
   if (!aGetTopVisibleLeaf && !frame->GetRect().IsEmpty()) {
     rectVisibility =
       aPresShell->GetRectVisibility(frame,
-                                    nsRect(nsPoint(0,0), frame->GetSize()),
+                                    frame->GetRectRelativeToSelf(),
                                     minDistance);
 
     if (rectVisibility == nsRectVisibility_kVisible) {
-      // This is an early exit case, where we return true if and only if
-      // the range is actually rendered.
-      return IsRangeRendered(aPresShell, aPresContext, aRange);
+      // The primary frame of the range is visible, but we don't yet know if
+      // any of the rects of the range itself are visible. Check to see if at
+      // least one of the rects is visible.
+      bool atLeastOneRangeRectVisible = false;
+
+      nsIFrame* containerFrame =
+        nsLayoutUtils::GetContainingBlockForClientRect(frame);
+      RefPtr<DOMRectList> rects = aRange->GetClientRects(true, true);
+      for (uint32_t i = 0; i < rects->Length(); ++i) {
+        RefPtr<DOMRect> rect = rects->Item(i);
+        nsRect r(nsPresContext::CSSPixelsToAppUnits((float)rect->X()),
+                 nsPresContext::CSSPixelsToAppUnits((float)rect->Y()),
+                 nsPresContext::CSSPixelsToAppUnits((float)rect->Width()),
+                 nsPresContext::CSSPixelsToAppUnits((float)rect->Height()));
+
+        // r is relative to containerFrame; transform it back to frame, so we
+        // can do a proper visibility check that is cropped to all of frame's
+        // ancestor scroll frames.
+        nsLayoutUtils::TransformResult res =
+          nsLayoutUtils::TransformRect(containerFrame, frame, r);
+        if (res == nsLayoutUtils::TransformResult::TRANSFORM_SUCCEEDED) {
+          nsRectVisibility rangeRectVisibility =
+            aPresShell->GetRectVisibility(frame, r, minDistance);
+
+          if (rangeRectVisibility == nsRectVisibility_kVisible) {
+            atLeastOneRangeRectVisible = true;
+            break;
+          }
+        }
+      }
+
+      if (atLeastOneRangeRectVisible) {
+        // This is an early exit case, where we return true if and only if
+        // the range is actually rendered.
+        return IsRangeRendered(aPresShell, aPresContext, aRange);
+      }
     }
   }
 
   // Below this point, we know the range is not in the viewport.
 
   if (!aMustBeInViewPort) {
     // This is an early exit case because we don't care that that range
     // is out of viewport, so we return that the range is "visible".
--- a/toolkit/modules/tests/browser/browser.ini
+++ b/toolkit/modules/tests/browser/browser.ini
@@ -28,16 +28,17 @@ support-files =
 
 [browser_AsyncPrefs.js]
 [browser_Battery.js]
 [browser_BrowserUtils.js]
 [browser_Deprecated.js]
 [browser_Finder.js]
 [browser_Finder_hidden_textarea.js]
 [browser_Finder_offscreen_text.js]
+[browser_Finder_overflowed_onscreen.js]
 [browser_Finder_overflowed_textarea.js]
 [browser_Finder_pointer_events_none.js]
 [browser_Finder_vertical_text.js]
 [browser_FinderHighlighter.js]
 skip-if = debug || os = "linux"
 [browser_FinderHighlighter2.js]
 skip-if = debug || os = "linux"
 [browser_Geometry.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/browser/browser_Finder_overflowed_onscreen.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function test_vertical_text() {
+  const URI = '<body><div style="max-height: 100px; max-width: 100px; overflow: scroll;"><div style="padding-left: 100px; max-height: 100px; max-width: 200px; overflow: auto;">d<br/><br/><br/><br/>c----------------b<br/><br/><br/><br/>a</div></div></body>';
+  await BrowserTestUtils.withNewTab({ gBrowser, url: "data:text/html;charset=utf-8," + encodeURIComponent(URI) },
+    async function(browser) {
+      let finder = browser.finder;
+      let listener = {
+        onFindResult() {
+          ok(false, "callback wasn't replaced");
+        }
+      };
+      finder.addResultListener(listener);
+
+      function waitForFind() {
+        return new Promise(resolve => {
+          listener.onFindResult = resolve;
+        });
+      }
+
+      let targets = [
+        "a",
+        "b",
+        "c",
+        "d",
+      ];
+
+      for (let i = 0; i < targets.length; ++i) {
+        // Find the target text.
+        let target = targets[i];
+        let promiseFind = waitForFind();
+        finder.fastFind(target, false, false);
+        let findResult = await promiseFind;
+        isnot(findResult.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Found target text '" + target + "'.");
+      }
+
+      finder.removeResultListener(listener);
+    });
+});