Merge mozilla-central to mozilla-inbound.
authorCosmin Sabou <csabou@mozilla.com>
Wed, 09 Jan 2019 18:16:13 +0200
changeset 510196 1c36dba855691e0525a736fac6f93d9afb679abb
parent 510195 0fe293d9f49475448cf3d1a58d0fa63d0d22bc9f (current diff)
parent 510149 29b2c2f578797279a9d55764da73158136081d89 (diff)
child 510197 de454ec382003d9b2d1f229fbacf215e068808cb
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone66.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 mozilla-inbound.
devtools/client/shared/multi-e10s-helper.js
devtools/shared/multi-e10s-helper.js
editor/reftests/xul/emptytextbox-3.xul
editor/reftests/xul/number-1.xul
editor/reftests/xul/number-2.xul
editor/reftests/xul/number-3.xul
editor/reftests/xul/number-4.xul
editor/reftests/xul/number-5.xul
editor/reftests/xul/number-ref.xul
editor/reftests/xul/numberwithvalue-1.xul
editor/reftests/xul/numberwithvalue-ref.xul
toolkit/content/tests/chrome/test_textbox_number.xul
toolkit/content/widgets/numberbox.xml
toolkit/themes/linux/global/global.css
toolkit/themes/shared/numberbox.css
toolkit/themes/shared/numberinput.css
toolkit/themes/windows/global/global.css
--- a/accessible/tests/mochitest/name/test_general.xul
+++ b/accessible/tests/mochitest/name/test_general.xul
@@ -233,17 +233,17 @@
 
   <!-- aria-labelledby, multiple relations -->
   <box class="third" id="labelledby_box_anon" role="group" />
 
   <!-- trick aria-labelledby -->
   <checkbox id="rememberHistoryDays"
             label="Remember "
             aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
-  <textbox id="historyDays" type="number" value="3"
+  <textbox id="historyDays" value="3"
            aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
   <label id="rememberAfter">days</label>
 
   <!-- the name from subtree, mixed content -->
   <description id="labelledby_mixed">
     no<description>more text</description>
   </description>
   <button id="btn_labelledby_mixed"
--- a/browser/components/preferences/connection.xul
+++ b/browser/components/preferences/connection.xul
@@ -63,18 +63,18 @@
           <row align="center">
             <hbox pack="end">
               <label data-l10n-id="connection-proxy-http" control="networkProxyHTTP" />
             </hbox>
             <hbox align="center">
               <textbox id="networkProxyHTTP" flex="1"
                        preference="network.proxy.http" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyServer();"/>
               <label data-l10n-id="connection-proxy-http-port" control="networkProxyHTTP_Port" />
-              <textbox id="networkProxyHTTP_Port" class="proxy-port-input" type="number" max="65535" hidespinbuttons="true"
-                       preference="network.proxy.http_port" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyPort();"/>
+              <html:input id="networkProxyHTTP_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535"
+                          preference="network.proxy.http_port" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyPort();"/>
             </hbox>
           </row>
           <row>
             <hbox/>
             <hbox>
               <checkbox id="shareAllProxies" data-l10n-id="connection-proxy-http-share"
                         preference="network.proxy.share_proxy_settings"
                         onsyncfrompreference="return gConnectionsDialog.updateProtocolPrefs();"/>
@@ -83,42 +83,42 @@
           <row align="center">
             <hbox pack="end">
               <label data-l10n-id="connection-proxy-ssl" control="networkProxySSL"/>
             </hbox>
             <hbox align="center">
               <textbox id="networkProxySSL" flex="1" preference="network.proxy.ssl"
                        onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', false);"/>
               <label data-l10n-id="connection-proxy-ssl-port" control="networkProxySSL_Port" />
-              <textbox id="networkProxySSL_Port" class="proxy-port-input" type="number" max="65535" size="5" preference="network.proxy.ssl_port"
-                       hidespinbuttons="true" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', true);"/>
+              <html:input id="networkProxySSL_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
+                          preference="network.proxy.ssl_port" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', true);"/>
             </hbox>
           </row>
           <row align="center">
             <hbox pack="end">
               <label data-l10n-id="connection-proxy-ftp" control="networkProxyFTP"/>
             </hbox>
             <hbox align="center">
               <textbox id="networkProxyFTP" flex="1" preference="network.proxy.ftp"
                        onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', false);"/>
               <label data-l10n-id="connection-proxy-ftp-port" control="networkProxyFTP_Port"/>
-              <textbox id="networkProxyFTP_Port" class="proxy-port-input" type="number" max="65535" size="5" preference="network.proxy.ftp_port"
-                       hidespinbuttons="true" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', true);"/>
+              <html:input id="networkProxyFTP_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
+                          preference="network.proxy.ftp_port" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', true);"/>
             </hbox>
           </row>
           <row align="center">
             <hbox pack="end">
               <label data-l10n-id="connection-proxy-socks" control="networkProxySOCKS"/>
             </hbox>
             <hbox align="center">
               <textbox id="networkProxySOCKS" flex="1" preference="network.proxy.socks"
                        onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', false);"/>
               <label data-l10n-id="connection-proxy-socks-port" control="networkProxySOCKS_Port"/>
-              <textbox id="networkProxySOCKS_Port" class="proxy-port-input" type="number" max="65535" size="5" preference="network.proxy.socks_port"
-                       hidespinbuttons="true" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', true);"/>
+              <html:input id="networkProxySOCKS_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
+                          preference="network.proxy.socks_port" onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', true);"/>
             </hbox>
           </row>
           <row>
             <spacer/>
             <box pack="start">
             <radiogroup id="networkProxySOCKSVersion" orient="horizontal"
                         preference="network.proxy.socks_version">
               <radio id="networkProxySOCKSVersion4" value="4" data-l10n-id="connection-proxy-socks4" />
--- a/browser/installer/allowed-dupes.mn
+++ b/browser/installer/allowed-dupes.mn
@@ -90,17 +90,17 @@ chrome/toolkit/skin/classic/global/autoc
 chrome/toolkit/skin/classic/global/button.css
 chrome/toolkit/skin/classic/global/checkbox.css
 chrome/toolkit/skin/classic/global/dialog.css
 chrome/toolkit/skin/classic/global/dropmarker.css
 chrome/toolkit/skin/classic/global/global.css
 chrome/toolkit/skin/classic/global/icons/close-win7.png
 chrome/toolkit/skin/classic/global/menu.css
 chrome/toolkit/skin/classic/global/menulist.css
-chrome/toolkit/skin/classic/global/numberbox.css
+chrome/toolkit/skin/classic/global/numberinput.css
 chrome/toolkit/skin/classic/global/popup.css
 chrome/toolkit/skin/classic/global/preferences.css
 chrome/toolkit/skin/classic/global/radio.css
 chrome/toolkit/skin/classic/global/richlistbox.css
 chrome/toolkit/skin/classic/global/scrollbars.css
 chrome/toolkit/skin/classic/global/scrollbox.css
 chrome/toolkit/skin/classic/global/splitter.css
 chrome/toolkit/skin/classic/global/tabbox.css
--- a/browser/modules/test/browser/browser_UsageTelemetry.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry.js
@@ -15,33 +15,34 @@ ChromeUtils.defineModuleGetter(this, "MI
 
 // Reset internal URI counter in case URIs were opened by other tests.
 Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC);
 
 /**
  * Get a snapshot of the scalars and check them against the provided values.
  */
 let checkScalars = (countsObject) => {
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   // Check the expected values. Scalars that are never set must not be reported.
-  checkScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
-              "The maximum tab count must match the expected value.");
-  checkScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
-              "The number of open tab event count must match the expected value.");
-  checkScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
-              "The maximum window count must match the expected value.");
-  checkScalar(scalars, WINDOW_OPEN_COUNT, countsObject.windowsOpenCount,
-              "The number of window open event count must match the expected value.");
-  checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
-              "The total URI count must match the expected value.");
-  checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
-              "The unique domains count must match the expected value.");
-  checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
-              "The unfiltered URI count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
+    "The maximum tab count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
+    "The number of open tab event count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
+    "The maximum window count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, WINDOW_OPEN_COUNT, countsObject.windowsOpenCount,
+    "The number of window open event count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
+    "The total URI count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
+    "The unique domains count must match the expected value.");
+  TelemetryTestUtils.assertScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+    "The unfiltered URI count must match the expected value.");
 };
 
 add_task(async function test_tabsAndWindows() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let openedTabs = [];
   let expectedTabOpenCount = 0;
@@ -136,17 +137,17 @@ add_task(async function test_subsessionS
 });
 
 function checkTabCountHistogram(result, expected, message) {
   Assert.deepEqual(result.values, expected, message);
 }
 
 add_task(async function test_tabsHistogram() {
   let openedTabs = [];
-  let tabCountHist = getAndClearHistogram("TAB_COUNT");
+  let tabCountHist = TelemetryTestUtils.getAndClearHistogram("TAB_COUNT");
 
   checkTabCountHistogram(tabCountHist.snapshot(), {}, "TAB_COUNT telemetry - initial tab counts");
 
   // Add a new tab and check that the count is right.
   BrowserUsageTelemetry._lastRecordTabCount = 0;
   openedTabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
   checkTabCountHistogram(tabCountHist.snapshot(), {1: 0, 2: 1, 3: 0}, "TAB_COUNT telemetry - opening tabs");
 
--- a/browser/modules/test/browser/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content.js
@@ -44,17 +44,17 @@ add_task(async function setup() {
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 add_task(async function test_context_menu() {
   // Let's reset the Telemetry data.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   // Open a new tab with a page containing some text.
   let tab =
     await BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/plain;charset=utf8,test%20search");
 
   info("Select all the text in the page.");
   await ContentTask.spawn(tab.linkedBrowser, "", async function() {
     return new Promise(resolve => {
@@ -70,59 +70,65 @@ add_task(async function test_context_men
                                            gBrowser.selectedBrowser);
   await popupPromise;
 
   info("Click on search.");
   let searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
   searchItem.click();
 
   info("Validate the search metrics.");
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
   Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.contextmenu", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.contextmenu", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}],
+  ]);
 
   contextMenu.hidePopup();
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
   BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_about_newtab() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
   await ContentTask.spawn(tab.linkedBrowser, null, async function() {
     await ContentTaskUtils.waitForCondition(() => !content.document.hidden);
   });
 
   info("Trigger a simple serch, just text + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
   await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.newtab", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.newtab", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}],
+  ]);
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -44,17 +44,17 @@ add_task(async function setup() {
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 add_task(async function test_abouthome_activitystream_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
   info("Load about:home.");
   BrowserTestUtils.loadURI(tab.linkedBrowser, "about:home");
   await BrowserTestUtils.browserStopped(tab.linkedBrowser, "about:home");
 
   info("Wait for ContentSearchUI search provider to initialize.");
@@ -64,23 +64,26 @@ add_task(async function test_abouthome_a
 
   info("Trigger a simple search, just test + enter.");
   let p = BrowserTestUtils.browserStopped(tab.linkedBrowser, "http://example.com/?q=test+query");
   await typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
   await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
     "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.abouthome", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.abouthome", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}],
+  ]);
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutRestartRequired.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutRestartRequired.js
@@ -6,25 +6,25 @@ add_task(async function test_aboutRestar
   let CrashHandlers = {};
   ChromeUtils.import("resource:///modules/ContentCrashHandlers.jsm",
                      CrashHandlers);
 
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let scalars =
-    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT);
+    TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT);
 
   // Check preconditions
   is(scalars[SCALAR_BUILDID_MISMATCH], undefined,
      "Build ID mismatch count should be undefined");
 
   // Simulate buildID mismatch
   CrashHandlers.TabCrashHandler._crashedTabCount = 1;
   CrashHandlers.TabCrashHandler.sendToRestartRequiredPage(
     gBrowser.selectedTab.linkedBrowser);
 
   scalars =
-    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT);
+    TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT);
 
   is(scalars[SCALAR_BUILDID_MISMATCH], 1,
      "Build ID mismatch count should be 1.");
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_domains.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_domains.js
@@ -44,23 +44,24 @@ function browserLocationChanged(browser)
 }
 
 add_task(async function test_URIAndDomainCounts() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
   let checkCounts = (countsObject) => {
     // Get a snapshot of the scalars and then clear them.
-    const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
-    checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
-                "The URI scalar must contain the expected value.");
-    checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
-                "The unique domains scalar must contain the expected value.");
-    checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
-                "The unfiltered URI scalar must contain the expected value.");
+    const scalars = TelemetryTestUtils.getParentProcessScalars(
+      Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    TelemetryTestUtils.assertScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
+      "The URI scalar must contain the expected value.");
+    TelemetryTestUtils.assertScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
+      "The unique domains scalar must contain the expected value.");
+    TelemetryTestUtils.assertScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+      "The unfiltered URI scalar must contain the expected value.");
   };
 
   // Check that about:blank doesn't get counted in the URI total.
   let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
   checkCounts({totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});
 
   // Open a different page and check the counts.
   await BrowserTestUtils.loadURI(firstTab.linkedBrowser, "http://example.com/");
--- a/browser/modules/test/browser/browser_UsageTelemetry_private_and_restore.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_private_and_restore.js
@@ -25,17 +25,18 @@ add_task(async function test_privateMode
   Services.telemetry.clearScalars();
 
   // Open a private window and load a website in it.
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   await BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
   await BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
 
   // Check that tab and window count is recorded.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs in private mode.");
   ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs in private mode.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains in private mode.");
   is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value.");
   is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value.");
   is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value.");
@@ -74,17 +75,18 @@ add_task(async function test_sessionRest
 
   // Load the custom state and wait for SSTabRestored, as we want to make sure
   // that the URI counting code was hit.
   let tabRestored = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored");
   SessionStore.setBrowserState(JSON.stringify(state));
   await tabRestored;
 
   // Check that the URI is not recorded.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
   ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
   ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains from restored sessions.");
 
   // Restore the original session and cleanup.
   let sessionRestored = promiseBrowserStateRestored();
   SessionStore.setBrowserState(JSON.stringify(state));
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -92,100 +92,111 @@ add_task(async function setup() {
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(async function test_plainQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let search_hist =
+    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("simple query");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.searchbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}],
+  ]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enter,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using the first result, a one-off button, and the Return
 // (Enter) key.
 add_task(async function test_oneOff_enter() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let search_hist =
+    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
 
   info("Pressing Alt+Down to highlight the first one off engine.");
   EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch2.searchbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch2.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}],
+  ]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enter,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using the second result, a one-off button, and the Return
 // (Enter) key.  This only tests the FX_SEARCHBAR_SELECTED_RESULT_METHOD
 // histogram since test_oneOff_enter covers everything else.
 add_task(async function test_oneOff_enterSelection() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = await new Promise((resolve, reject) => {
     Services.search.addEngine(url, "", false, {
       onSuccess(engine) { resolve(engine); },
       onError() { reject(); },
@@ -218,17 +229,18 @@ add_task(async function test_oneOff_ente
 });
 
 // Performs a search using a click on a one-off button.  This only tests the
 // FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
 // everything else.
 add_task(async function test_oneOff_click() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Type a query.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
   info("Click the first one-off button.");
   BrowserSearch.searchBar.textbox.popup.oneOffButtons.getSelectableButtons(false)[0].click();
@@ -242,18 +254,20 @@ add_task(async function test_oneOff_clic
   BrowserTestUtils.removeTab(tab);
 });
 
 // Clicks the first suggestion offered by the test search engine.
 add_task(async function test_suggestion_click() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let search_hist =
+    TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = await new Promise((resolve, reject) => {
     Services.search.addEngine(url, "", false, {
       onSuccess(engine) { resolve(engine); },
       onError() { reject(); },
@@ -268,29 +282,32 @@ add_task(async function test_suggestion_
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
   info("Clicking the searchbar suggestion.");
   clickSearchbarSuggestion("queryfoo");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_suggestion", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_suggestion", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   let searchEngineId = "other-" + suggestionEngine.name;
-  checkKeyedHistogram(search_hist, searchEngineId + ".searchbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, searchEngineId + ".searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
+  TelemetryTestUtils.assertEvents(events, [
+    ["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}],
+  ]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.click,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   Services.search.defaultEngine = previousEngine;
@@ -300,17 +317,18 @@ add_task(async function test_suggestion_
 
 // Selects and presses the Return (Enter) key on the first suggestion offered by
 // the test search engine.  This only tests the
 // FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_suggestion_click
 // covers everything else.
 add_task(async function test_suggestion_enterSelection() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist =
+    TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = await new Promise((resolve, reject) => {
     Services.search.addEngine(url, "", false, {
       onSuccess(engine) { resolve(engine); },
       onError() { reject(); },
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -12,28 +12,16 @@ ChromeUtils.defineModuleGetter(this, "UR
                                "resource:///modules/BrowserUsageTelemetry.jsm");
 
 ChromeUtils.defineModuleGetter(this, "URLBAR_SELECTED_RESULT_METHODS",
                                "resource:///modules/BrowserUsageTelemetry.jsm");
 
 ChromeUtils.defineModuleGetter(this, "SearchTelemetry",
                               "resource:///modules/SearchTelemetry.jsm");
 
-function checkHistogramResults(resultIndexes, expected, histogram) {
-  for (let [i, val] of Object.entries(resultIndexes.values)) {
-    if (i == expected) {
-      Assert.equal(val, 1,
-        `expected counts should match for ${histogram} index ${i}`);
-    } else {
-      Assert.equal(!!val, false,
-        `unexpected counts should be zero for ${histogram} index ${i}`);
-    }
-  }
-}
-
 let searchInAwesomebar = async function(inputText, win = window) {
   await new Promise(r => waitForFocus(r, win));
   // Write the search query in the urlbar.
   win.gURLBar.focus();
   win.gURLBar.value = inputText;
 
   // This is not strictly necessary, but some things, like clearing oneoff
   // buttons status, depend on actual input events that the user would normally
@@ -154,530 +142,490 @@ add_task(async function setup() {
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(async function test_simpleQuery() {
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
-  let resultIndexHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
-  let resultTypeHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
-  let resultIndexByTypeHist = getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultIndexHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
+  let resultTypeHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
+  let resultIndexByTypeHist = TelemetryTestUtils.getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("simple query");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "other-MozSearch.alias", undefined);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.alias", undefined);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  TelemetryTestUtils.assertHistogram(resultIndexHist, 0, 1);
 
-  let resultTypes = resultTypeHist.snapshot();
-  checkHistogramResults(resultTypes,
-    URLBAR_SELECTED_RESULT_TYPES.searchengine,
-    "FX_URLBAR_SELECTED_RESULT_TYPE");
+  TelemetryTestUtils.assertHistogram(resultTypeHist,
+    URLBAR_SELECTED_RESULT_TYPES.searchengine, 1);
 
-  let resultIndexByType = resultIndexByTypeHist.snapshot().searchengine;
-  checkHistogramResults(resultIndexByType,
-    0,
-    "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  TelemetryTestUtils.assertKeyedHistogramValue(resultIndexByTypeHist,
+    "searchengine", 0, 1);
 
-  let resultMethods = resultMethodHist.snapshot();
-  checkHistogramResults(resultMethods,
-    URLBAR_SELECTED_RESULT_METHODS.enter,
-    "FX_URLBAR_SELECTED_RESULT_METHOD");
+  TelemetryTestUtils.assertHistogram(resultMethodHist,
+    URLBAR_SELECTED_RESULT_METHODS.enter, 1);
 
   BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_searchAlias() {
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
-  let resultIndexHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
-  let resultTypeHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
-  let resultIndexByTypeHist = getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultIndexHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
+  let resultTypeHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
+  let resultIndexByTypeHist = TelemetryTestUtils.getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Search using a search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("mozalias query");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "other-MozSearch.alias", undefined);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.alias", undefined);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  TelemetryTestUtils.assertHistogram(resultIndexHist, 0, 1);
 
-  let resultTypes = resultTypeHist.snapshot();
-  checkHistogramResults(resultTypes,
-    URLBAR_SELECTED_RESULT_TYPES.searchengine,
-    "FX_URLBAR_SELECTED_RESULT_TYPE");
+  TelemetryTestUtils.assertHistogram(resultTypeHist,
+    URLBAR_SELECTED_RESULT_TYPES.searchengine, 1);
 
-  let resultIndexByType = resultIndexByTypeHist.snapshot().searchengine;
-  checkHistogramResults(resultIndexByType,
-    0,
-    "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  TelemetryTestUtils.assertKeyedHistogramValue(resultIndexByTypeHist,
+    "searchengine", 0, 1);
 
-  let resultMethods = resultMethodHist.snapshot();
-  checkHistogramResults(resultMethods,
-    URLBAR_SELECTED_RESULT_METHODS.enter,
-    "FX_URLBAR_SELECTED_RESULT_METHOD");
+  TelemetryTestUtils.assertHistogram(resultMethodHist,
+    URLBAR_SELECTED_RESULT_METHODS.enter, 1);
 
   BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_internalSearchAlias() {
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Search using an internal search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("@mozaliasfoo query");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "other-MozSearch.alias", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.alias", 1);
 
   info("Search using the other internal search alias.");
   p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("@mozaliasbar query");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 2);
-  checkKeyedHistogram(search_hist, "other-MozSearch.alias", 2);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 2);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.alias", 2);
 
   BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using the first result, a one-off button, and the Return
 // (Enter) key.
 add_task(async function test_oneOff_enter() {
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
-  let resultIndexHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
-  let resultTypeHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
-  let resultIndexByTypeHist = getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultIndexHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
+  let resultTypeHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
+  let resultIndexByTypeHist = TelemetryTestUtils.getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("query");
 
   info("Pressing Alt+Down to take us to the first one-off engine.");
   EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   // Check if the scalars contain the expected values.
-  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-  checkKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
+  const scalars = TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+  TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "other-MozSearch.alias", undefined);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.alias", undefined);
 
   // Also check events.
   let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-  checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
+  TelemetryTestUtils.assertEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
 
   // Check the histograms as well.
-  let resultIndexes = resultIndexHist.snapshot();
-  checkHistogramResults(resultIndexes, 0, "FX_URLBAR_SELECTED_RESULT_INDEX");
+  TelemetryTestUtils.assertHistogram(resultIndexHist, 0, 1);
 
-  let resultTypes = resultTypeHist.snapshot();
-  checkHistogramResults(resultTypes,
-    URLBAR_SELECTED_RESULT_TYPES.searchengine,
-    "FX_URLBAR_SELECTED_RESULT_TYPE");
+  TelemetryTestUtils.assertHistogram(resultTypeHist,
+    URLBAR_SELECTED_RESULT_TYPES.searchengine, 1);
 
-  let resultIndexByType = resultIndexByTypeHist.snapshot().searchengine;
-  checkHistogramResults(resultIndexByType,
-    0,
-    "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  TelemetryTestUtils.assertKeyedHistogramValue(resultIndexByTypeHist,
+    "searchengine", 0, 1);
 
-  let resultMethods = resultMethodHist.snapshot();
-  checkHistogramResults(resultMethods,
-    URLBAR_SELECTED_RESULT_METHODS.enter,
-    "FX_URLBAR_SELECTED_RESULT_METHOD");
+  TelemetryTestUtils.assertHistogram(resultMethodHist,
+    URLBAR_SELECTED_RESULT_METHODS.enter, 1);
 
   BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using the second result, a one-off button, and the Return
 // (Enter) key.  This only tests the FX_URLBAR_SELECTED_RESULT_METHOD histogram
 // since test_oneOff_enter covers everything else.
 add_task(async function test_oneOff_enterSelection() {
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   await withNewSearchEngine(async function() {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
 
     info("Select the second result, press Alt+Down to take us to the first one-off engine.");
     EventUtils.synthesizeKey("KEY_ArrowDown");
     EventUtils.synthesizeKey("KEY_ArrowDown", {altKey: true});
     EventUtils.synthesizeKey("KEY_Enter");
     await p;
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 // Performs a search using a click on a one-off button.  This only tests the
 // FX_URLBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
 // everything else.
 add_task(async function test_oneOff_click() {
   Services.telemetry.clearScalars();
 
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Type a query.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInAwesomebar("query");
   info("Click the first one-off button.");
   gURLBar.popup.oneOffSearchButtons.getSelectableButtons(false)[0].click();
   await p;
 
-  let resultMethods = resultMethodHist.snapshot();
-  checkHistogramResults(resultMethods,
-    URLBAR_SELECTED_RESULT_METHODS.click,
-    "FX_URLBAR_SELECTED_RESULT_METHOD");
+  TelemetryTestUtils.assertHistogram(resultMethodHist,
+    URLBAR_SELECTED_RESULT_METHODS.click, 1);
 
   BrowserTestUtils.removeTab(tab);
 });
 
 // Clicks the first suggestion offered by the test search engine.
 add_task(async function test_suggestion_click() {
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
-  let resultIndexHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
-  let resultTypeHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
-  let resultIndexByTypeHist = getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let resultIndexHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_INDEX");
+  let resultTypeHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_TYPE");
+  let resultIndexByTypeHist = TelemetryTestUtils.getAndClearKeyedHistogram("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   await withNewSearchEngine(async function(engine) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
     info("Clicking the urlbar suggestion.");
     await clickURLBarSuggestion("queryfoo");
     await p;
 
     // Check if the scalars contain the expected values.
-    const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
-    checkKeyedScalar(scalars, SCALAR_URLBAR, "search_suggestion", 1);
+    const scalars = TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
+    TelemetryTestUtils.assertKeyedScalar(scalars, SCALAR_URLBAR, "search_suggestion", 1);
     Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                 "This search must only increment one entry in the scalar.");
 
     // Make sure SEARCH_COUNTS contains identical values.
     let searchEngineId = "other-" + engine.name;
-    checkKeyedHistogram(search_hist, searchEngineId + ".urlbar", 1);
+    TelemetryTestUtils.assertKeyedHistogramSum(search_hist, searchEngineId + ".urlbar", 1);
 
     // Also check events.
     let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
     events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
-    checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
+    TelemetryTestUtils.assertEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
 
     // Check the histograms as well.
-    let resultIndexes = resultIndexHist.snapshot();
-    checkHistogramResults(resultIndexes, 3, "FX_URLBAR_SELECTED_RESULT_INDEX");
+    TelemetryTestUtils.assertHistogram(resultIndexHist, 3, 1);
 
-    let resultTypes = resultTypeHist.snapshot();
-    checkHistogramResults(resultTypes,
-      URLBAR_SELECTED_RESULT_TYPES.searchsuggestion,
-      "FX_URLBAR_SELECTED_RESULT_TYPE");
+    TelemetryTestUtils.assertHistogram(resultTypeHist,
+      URLBAR_SELECTED_RESULT_TYPES.searchsuggestion, 1);
 
-    let resultIndexByType = resultIndexByTypeHist.snapshot().searchsuggestion;
-    checkHistogramResults(resultIndexByType,
-      3,
-      "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+    TelemetryTestUtils.assertKeyedHistogramValue(resultIndexByTypeHist,
+      "searchsuggestion", 3, 1);
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.click,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.click, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 // Selects and presses the Return (Enter) key on the first suggestion offered by
 // the test search engine.  This only tests the FX_URLBAR_SELECTED_RESULT_METHOD
 // histogram since test_suggestion_click covers everything else.
 add_task(async function test_suggestion_arrowEnterSelection() {
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   await withNewSearchEngine(async function() {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
     info("Select the second result and press Return.");
     EventUtils.synthesizeKey("KEY_ArrowDown");
     EventUtils.synthesizeKey("KEY_Enter");
     await p;
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.arrowEnterSelection, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 // Selects through tab and presses the Return (Enter) key on the first
 // suggestion offered by the test search engine.
 add_task(async function test_suggestion_tabEnterSelection() {
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   await withNewSearchEngine(async function() {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
     info("Select the second result and press Return.");
     EventUtils.synthesizeKey("KEY_Tab");
     EventUtils.synthesizeKey("KEY_Enter");
     await p;
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.tabEnterSelection,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.tabEnterSelection, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 // Selects through code and presses the Return (Enter) key on the first
 // suggestion offered by the test search engine.
 add_task(async function test_suggestion_enterSelection() {
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   await withNewSearchEngine(async function() {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
     info("Select the second result and press Return.");
     gURLBar.popup.selectedIndex = 1;
     EventUtils.synthesizeKey("KEY_Enter");
     await p;
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.enterSelection,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.enterSelection, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 // Selects through mouse right button and press the Return (Enter) key.
 add_task(async function test_suggestion_rightclick() {
   Services.telemetry.clearScalars();
-  let resultMethodHist = getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram("FX_URLBAR_SELECTED_RESULT_METHOD");
 
   await withNewSearchEngine(async function() {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
     info("Type a query. Suggestions should be generated by the test engine.");
     let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
     await searchInAwesomebar("query");
     info("Right click the the second result and then press Return.");
     await clickURLBarSuggestion("queryfoo", 2);
     EventUtils.synthesizeKey("KEY_Enter");
     await p;
 
-    let resultMethods = resultMethodHist.snapshot();
-    checkHistogramResults(resultMethods,
-      URLBAR_SELECTED_RESULT_METHODS.rightClickEnter,
-      "FX_URLBAR_SELECTED_RESULT_METHOD");
+    TelemetryTestUtils.assertHistogram(resultMethodHist,
+      URLBAR_SELECTED_RESULT_METHODS.rightClickEnter, 1);
 
     BrowserTestUtils.removeTab(tab);
   });
 });
 
 add_task(async function test_privateWindow() {
   // Mock the search telemetry search provider info so that its
   // recordSearchURLTelemetry() function adds the in-content SEARCH_COUNTS
   // telemetry for our test engine.
   SearchTelemetry.overrideSearchTelemetryForTests({
     "example": {
       "regexp": "^http://example\\.com/",
       "queryParam": "q",
     },
   });
 
-  let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
+  let search_hist = TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   // First, do a bunch of searches in a private window.
   let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
 
   info("Search in a private window and the pref does not exist");
   let p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 1);
 
   info("Search again in a private window after setting the pref to true");
   Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", true);
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should *not* be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 1);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 1);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 1);
 
   info("Search again in a private window after setting the pref to false");
   Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", false);
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 2);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 2);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 2);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 2);
 
   info("Search again in a private window after clearing the pref");
   Services.prefs.clearUserPref("browser.engagement.search_counts.pbm");
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 3);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 3);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 3);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 3);
 
   await BrowserTestUtils.closeWindow(win);
 
   // Now, do a bunch of searches in a non-private window.  Telemetry should
   // always be recorded regardless of the pref's value.
   win = await BrowserTestUtils.openNewBrowserWindow();
 
   info("Search in a non-private window and the pref does not exist");
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 4);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 4);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 4);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 4);
 
   info("Search again in a non-private window after setting the pref to true");
   Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", true);
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 5);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 5);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 5);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 5);
 
   info("Search again in a non-private window after setting the pref to false");
   Services.prefs.setBoolPref("browser.engagement.search_counts.pbm", false);
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 6);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 6);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 6);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 6);
 
   info("Search again in a non-private window after clearing the pref");
   Services.prefs.clearUserPref("browser.engagement.search_counts.pbm");
   p = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   await searchInAwesomebar("another query", win);
   EventUtils.synthesizeKey("KEY_Enter", undefined, win);
   await p;
 
   // SEARCH_COUNTS should be incremented.
-  checkKeyedHistogram(search_hist, "other-MozSearch.urlbar", 7);
-  checkKeyedHistogram(search_hist, "example.in-content:organic:none", 7);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "other-MozSearch.urlbar", 7);
+  TelemetryTestUtils.assertKeyedHistogramSum(search_hist, "example.in-content:organic:none", 7);
 
   await BrowserTestUtils.closeWindow(win);
 
   // Reset the search provider info.
   SearchTelemetry.overrideSearchTelemetryForTests();
 });
--- a/browser/modules/test/browser/head.js
+++ b/browser/modules/test/browser/head.js
@@ -1,11 +1,13 @@
 
 ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
                                "resource://testing-common/PlacesTestUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "TelemetryTestUtils",
+                               "resource://testing-common/TelemetryTestUtils.jsm");
 
 const SINGLE_TRY_TIMEOUT = 100;
 const NUMBER_OF_TRIES = 30;
 
 function waitForConditionPromise(condition, timeoutMsg, tryCount = NUMBER_OF_TRIES) {
   return new Promise((resolve, reject) => {
     let tries = 0;
     function checkCondition() {
@@ -31,59 +33,16 @@ function waitForConditionPromise(conditi
 
 function waitForCondition(condition, nextTest, errorMsg) {
   waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
     ok(false, reason + (reason.stack ? "\n" + reason.stack : ""));
   });
 }
 
 /**
- * Checks if the snapshotted keyed scalars contain the expected
- * data.
- *
- * @param {Object} scalars
- *        The snapshot of the keyed scalars.
- * @param {String} scalarName
- *        The name of the keyed scalar to check.
- * @param {String} key
- *        The key that must be within the keyed scalar.
- * @param {String|Boolean|Number} expectedValue
- *        The expected value for the provided key in the scalar.
- */
-function checkKeyedScalar(scalars, scalarName, key, expectedValue) {
-  Assert.ok(scalarName in scalars,
-            scalarName + " must be recorded.");
-  Assert.ok(key in scalars[scalarName],
-            scalarName + " must contain the '" + key + "' key.");
-  Assert.equal(scalars[scalarName][key], expectedValue,
-            scalarName + "['" + key + "'] must contain the expected value");
-}
-
-/**
- * An helper that checks the value of a scalar if it's expected to be > 0,
- * otherwise makes sure that the scalar it's not reported.
- *
- * @param {Object} scalars
- *        The snapshot of the scalars.
- * @param {String} scalarName
- *        The name of the scalar to check.
- * @param {Number} value
- *        The expected value for the provided scalar.
- * @param {String} msg
- *        The message to print when checking the value.
- */
-let checkScalar = (scalars, scalarName, value, msg) => {
-  if (value > 0) {
-    is(scalars[scalarName], value, msg);
-    return;
-  }
-  ok(!(scalarName in scalars), scalarName + " must not be reported.");
-};
-
-/**
  * An utility function to write some text in the search input box
  * in a content page.
  * @param {Object} browser
  *        The browser that contains the content.
  * @param {String} text
  *        The string to write in the search field.
  * @param {String} fieldName
  *        The name of the field to write to.
@@ -92,84 +51,16 @@ let typeInSearchField = async function(b
   await ContentTask.spawn(browser, [fieldName, text], async function([contentFieldName, contentText]) {
     // Put the focus on the search box.
     let searchInput = content.document.getElementById(contentFieldName);
     searchInput.focus();
     searchInput.value = contentText;
   });
 };
 
-
-/**
- * Clear and get the named histogram
- * @param {String} name
- *        The name of the histogram
- */
-function getAndClearHistogram(name) {
-  let histogram = Services.telemetry.getHistogramById(name);
-  histogram.clear();
-  return histogram;
-}
-
-
-/**
- * Clear and get the named keyed histogram
- * @param {String} name
- *        The name of the keyed histogram
- */
-function getAndClearKeyedHistogram(name) {
-  let histogram = Services.telemetry.getKeyedHistogramById(name);
-  histogram.clear();
-  return histogram;
-}
-
-
-/**
- * Check that the keyed histogram contains the right value.
- */
-function checkKeyedHistogram(h, key, expectedValue) {
-  const snapshot = h.snapshot();
-  if (expectedValue === undefined) {
-    Assert.ok(!(key in snapshot), `The histogram must not contain ${key}.`);
-    return;
-  }
-  Assert.ok(key in snapshot, `The histogram must contain ${key}.`);
-  Assert.equal(snapshot[key].sum, expectedValue, `The key ${key} must contain ${expectedValue}.`);
-}
-
-/**
- * Return the scalars from the parent-process.
- */
-function getParentProcessScalars(aChannel, aKeyed = false, aClear = false) {
-  const extended = aChannel == Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
-  const currentExtended = Services.telemetry.canRecordExtended;
-  Services.telemetry.canRecordExtended = extended;
-  const scalars = aKeyed ?
-    Services.telemetry.getSnapshotForKeyedScalars("main", aClear).parent :
-    Services.telemetry.getSnapshotForScalars("main", aClear).parent;
-  Services.telemetry.canRecordExtended = currentExtended;
-  return scalars || {};
-}
-
-function checkEvents(events, expectedEvents) {
-  if (!Services.telemetry.canRecordExtended) {
-    // Currently we only collect the tested events when extended Telemetry is enabled.
-    return;
-  }
-
-  Assert.equal(events.length, expectedEvents.length, "Should have matching amount of events.");
-
-  // Strip timestamps from the events for easier comparison.
-  events = events.map(e => e.slice(1));
-
-  for (let i = 0; i < events.length; ++i) {
-    Assert.deepEqual(events[i], expectedEvents[i], "Events should match.");
-  }
-}
-
 /**
  * Given a <xul:browser> at some non-internal web page,
  * return something that resembles an nsIContentPermissionRequest,
  * using the browsers currently loaded document to get a principal.
  *
  * @param browser (<xul:browser>)
  *        The browser that we'll create a nsIContentPermissionRequest
  *        for.
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -42,20 +42,16 @@
   outline: 1px solid var(--arrowpanel-dimmed);
 }
 
 .downloadsPanelFooterButton:hover:active,
 .downloadsPanelFooterButton[open="true"] {
   outline: 1px solid var(--arrowpanel-dimmed-further);
 }
 
-.downloadsPanelFooterButton > .button-box {
-  padding: 0;
-}
-
 @notKeyfocus@ .downloadsPanelFooterButton:-moz-focusring {
   outline: none;
 }
 
 #downloadsSummary {
   /* Reserve the same space as the button and separator in download items. */
   padding-inline-end: 59px;
 }
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -844,17 +844,18 @@ menulist[indicator=true] > menupopup men
 
 #no-results-message[query*=🔥🦊] > .no-results-container {
   visibility: hidden;
 }
 
 /* Proxy port input */
 
 .proxy-port-input {
-  width: calc(5ch + 22px); /* 5 chars + 11px padding on both sides */
+  width: calc(5ch + 18px); /* 5 chars + (8px padding + 1px border) on both sides */
+  margin-inline-start: 4px;
 }
 
 #defaultBrowserLanguage {
   margin-inline-start: 0;
   min-width: 20em;
 }
 
 #selectedLocales {
--- a/build/build-clang/clang-7-android.json
+++ b/build/build-clang/clang-7-android.json
@@ -1,20 +1,20 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "2",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_701/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "android_targets": {
       "armv7-linux-android": {
         "ndk_toolchain": "/builds/worker/workspace/build/src/android-ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64",
--- a/build/build-clang/clang-7-linux64.json
+++ b/build/build-clang/clang-7-linux64.json
@@ -1,20 +1,20 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "3",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_701/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "static-llvm-symbolizer.patch",
       "find_symbolizer_linux.patch",
--- a/build/build-clang/clang-7-macosx64.json
+++ b/build/build-clang/clang-7-macosx64.json
@@ -1,21 +1,21 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "osx_cross_compile": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_701/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
     "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
     "as": "/builds/worker/workspace/build/src/clang/bin/clang",
     "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-ar",
     "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-ranlib",
     "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-libtool",
--- a/build/build-clang/clang-tidy-linux64.json
+++ b/build/build-clang/clang-tidy-linux64.json
@@ -1,20 +1,20 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final/",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final/",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/final/",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final/",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final/",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final/",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final/",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_701/final/",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final/",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_701/final/",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
     "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
     ]
 }
--- a/build/build-clang/clang-tidy-macosx64.json
+++ b/build/build-clang/clang-tidy-macosx64.json
@@ -1,21 +1,21 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "osx_cross_compile": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
-    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
+    "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_701/final",
     "python_path": "/usr/bin/python2.7",
     "gcc_dir": "/builds/worker/workspace/build/src/gcc",
     "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
     "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
     "as": "/builds/worker/workspace/build/src/clang/bin/clang",
     "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-ar",
     "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-ranlib",
     "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-darwin11-libtool",
--- a/build/build-clang/clang-tidy-win32.json
+++ b/build/build-clang/clang-tidy-win32.json
@@ -1,18 +1,18 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "patches": [
     ]
 }
--- a/build/build-clang/clang-tidy-win64.json
+++ b/build/build-clang/clang-tidy-win64.json
@@ -1,19 +1,19 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "ml": "ml64.exe",
     "patches": [
     ]
 }
--- a/build/build-clang/clang-win64.json
+++ b/build/build-clang/clang-win64.json
@@ -1,19 +1,19 @@
 {
-    "llvm_revision": "342383",
+    "llvm_revision": "349247",
     "stages": "3",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
-    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
-    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
-    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
-    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
-    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
+    "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_701/final",
+    "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_701/final",
+    "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_701/final",
+    "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_701/final",
+    "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_701/final",
     "python_path": "c:/mozilla-build/python/python.exe",
     "cc": "cl.exe",
     "cxx": "cl.exe",
     "ml": "ml64.exe",
     "patches": [
       "workaround-issue38586.patch",
       "r342649-hotpatch-8-byte-nops.patch",
       "r342652-unpoison-thread-stacks.patch",
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -27,39 +27,32 @@ const {
   removeNetworkLocationsObserver,
 } = require("./src/modules/network-locations");
 const {
   addUSBRuntimesObserver,
   getUSBRuntimes,
   removeUSBRuntimesObserver,
 } = require("./src/modules/usb-runtimes");
 
-const {
-  addMultiE10sListener,
-  isMultiE10s,
-  removeMultiE10sListener,
-} = require("devtools/client/shared/multi-e10s-helper");
-
 loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
 
 const Router = createFactory(require("devtools/client/shared/vendor/react-router-dom").HashRouter);
 const App = createFactory(require("./src/components/App"));
 
 const AboutDebugging = {
   async init() {
     if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
       // If DevTools are disabled, navigate to about:devtools.
       window.location = "about:devtools?reason=AboutDebugging";
       return;
     }
 
     this.onAdbAddonUpdated = this.onAdbAddonUpdated.bind(this);
     this.onNetworkLocationsUpdated = this.onNetworkLocationsUpdated.bind(this);
     this.onUSBRuntimesUpdated = this.onUSBRuntimesUpdated.bind(this);
-    this.onMultiE10sUpdated = this.onMultiE10sUpdated.bind(this);
 
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
     await l10n.init();
 
     render(
       Provider(
@@ -85,22 +78,16 @@ const AboutDebugging = {
     this.onUSBRuntimesUpdated();
     addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
 
     adbAddon.on("update", this.onAdbAddonUpdated);
     this.onAdbAddonUpdated();
 
     // Remove deprecated remote debugging extensions.
     await adbAddon.uninstallUnsupportedExtensions();
-
-    addMultiE10sListener(this.onMultiE10sUpdated);
-  },
-
-  onMultiE10sUpdated() {
-    this.actions.updateMultiE10sStatus(isMultiE10s());
   },
 
   onAdbAddonUpdated() {
     this.actions.updateAdbAddonStatus(adbAddon.status);
   },
 
   onNetworkLocationsUpdated() {
     this.actions.updateNetworkLocations(getNetworkLocations());
@@ -118,17 +105,16 @@ const AboutDebugging = {
     const currentRuntimeId = state.runtimes.selectedRuntimeId;
     if (currentRuntimeId) {
       await this.actions.unwatchRuntime(currentRuntimeId);
     }
 
     // Remove all client listeners.
     this.actions.removeRuntimeListeners();
 
-    removeMultiE10sListener(this.onMultiE10sUpdated);
     removeNetworkLocationsObserver(this.onNetworkLocationsUpdated);
     removeUSBRuntimesObserver(this.onUSBRuntimesUpdated);
     adbAddon.off("update", this.onAdbAddonUpdated);
     setDebugTargetCollapsibilities(state.ui.debugTargetCollapsibilities);
     unmountComponentAtNode(this.mount);
   },
 
   get mount() {
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -30,62 +30,80 @@ const {
   RUNTIME_PREFERENCE,
   RUNTIMES,
   UNWATCH_RUNTIME_FAILURE,
   UNWATCH_RUNTIME_START,
   UNWATCH_RUNTIME_SUCCESS,
   UPDATE_CONNECTION_PROMPT_SETTING_FAILURE,
   UPDATE_CONNECTION_PROMPT_SETTING_START,
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
+  UPDATE_RUNTIME_MULTIE10S_FAILURE,
+  UPDATE_RUNTIME_MULTIE10S_START,
+  UPDATE_RUNTIME_MULTIE10S_SUCCESS,
   WATCH_RUNTIME_FAILURE,
   WATCH_RUNTIME_START,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
 async function getRuntimeInfo(runtime, clientWrapper) {
   const { type } = runtime;
-  const { name, channel, deviceName, version } =
+  const { name, channel, deviceName, isMultiE10s, version } =
     await clientWrapper.getDeviceDescription();
   const icon =
     (channel === "release" || channel === "beta" || channel === "aurora")
       ? `chrome://devtools/skin/images/aboutdebugging-firefox-${ channel }.svg`
       : "chrome://devtools/skin/images/aboutdebugging-firefox-nightly.svg";
 
   return {
+    deviceName,
     icon,
-    deviceName,
+    isMultiE10s,
     name,
     type,
     version,
   };
 }
 
 function onUSBDebuggerClientClosed() {
   // After scanUSBRuntimes action, updateUSBRuntimes action is called.
   // The closed runtime will be unwatched and disconnected explicitly in the action
   // if needed.
   window.AboutDebugging.store.dispatch(Actions.scanUSBRuntimes());
 }
 
+function onMultiE10sUpdated() {
+  window.AboutDebugging.store.dispatch(updateMultiE10s());
+}
+
 function connectRuntime(id) {
   return async (dispatch, getState) => {
     dispatch({ type: CONNECT_RUNTIME_START });
     try {
       const runtime = findRuntimeById(id, getState().runtimes);
       const clientWrapper = await createClientForRuntime(runtime);
       const info = await getRuntimeInfo(runtime, clientWrapper);
+      const { isMultiE10s } = info;
+      delete info.isMultiE10s;
 
       const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
       const connectionPromptEnabled = await clientWrapper.getPreference(promptPrefName);
       const runtimeDetails = {
         clientWrapper,
         connectionPromptEnabled,
         info,
+        isMultiE10s,
       };
 
+      clientWrapper.addListener("closed", onUSBDebuggerClientClosed);
+
+      const deviceFront = await clientWrapper.getFront("device");
+      if (deviceFront) {
+        deviceFront.on("multi-e10s-updated", onMultiE10sUpdated);
+      }
+
       if (runtime.type === RUNTIMES.USB) {
         // `closed` event will be emitted when disabling remote debugging
         // on the connected USB runtime.
         clientWrapper.addOneTimeListener("closed", onUSBDebuggerClientClosed);
       }
 
       dispatch({
         type: CONNECT_RUNTIME_SUCCESS,
@@ -103,16 +121,21 @@ function connectRuntime(id) {
 
 function disconnectRuntime(id) {
   return async (dispatch, getState) => {
     dispatch({ type: DISCONNECT_RUNTIME_START });
     try {
       const runtime = findRuntimeById(id, getState().runtimes);
       const { clientWrapper } = runtime.runtimeDetails;
 
+      const deviceFront = await clientWrapper.getFront("device");
+      if (deviceFront) {
+        deviceFront.off("multi-e10s-updated", onMultiE10sUpdated);
+      }
+
       if (runtime.type === RUNTIMES.USB) {
         clientWrapper.removeListener("closed", onUSBDebuggerClientClosed);
       }
 
       await clientWrapper.close();
 
       dispatch({
         type: DISCONNECT_RUNTIME_SUCCESS,
@@ -141,16 +164,32 @@ function updateConnectionPromptSetting(c
       dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
                  runtime, connectionPromptEnabled });
     } catch (e) {
       dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_FAILURE, error: e });
     }
   };
 }
 
+function updateMultiE10s() {
+  return async (dispatch, getState) => {
+    dispatch({ type: UPDATE_RUNTIME_MULTIE10S_START });
+    try {
+      const runtime = getCurrentRuntime(getState().runtimes);
+      const { clientWrapper } = runtime.runtimeDetails;
+      // Re-get actual value from the runtime.
+      const { isMultiE10s } = await clientWrapper.getDeviceDescription();
+
+      dispatch({ type: UPDATE_RUNTIME_MULTIE10S_SUCCESS, runtime, isMultiE10s });
+    } catch (e) {
+      dispatch({ type: UPDATE_RUNTIME_MULTIE10S_FAILURE, error: e });
+    }
+  };
+}
+
 function watchRuntime(id) {
   return async (dispatch, getState) => {
     dispatch({ type: WATCH_RUNTIME_START });
 
     try {
       if (id === RUNTIMES.THIS_FIREFOX) {
         // THIS_FIREFOX connects and disconnects on the fly when opening the page.
         await dispatch(connectRuntime(RUNTIMES.THIS_FIREFOX));
--- a/devtools/client/aboutdebugging-new/src/actions/ui.js
+++ b/devtools/client/aboutdebugging-new/src/actions/ui.js
@@ -8,17 +8,16 @@ const {
   ADB_ADDON_INSTALL_START,
   ADB_ADDON_INSTALL_SUCCESS,
   ADB_ADDON_INSTALL_FAILURE,
   ADB_ADDON_UNINSTALL_START,
   ADB_ADDON_UNINSTALL_SUCCESS,
   ADB_ADDON_UNINSTALL_FAILURE,
   ADB_ADDON_STATUS_UPDATED,
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
-  MULTI_E10S_UPDATED,
   NETWORK_LOCATIONS_UPDATED,
   PAGE_SELECTED,
   PAGE_TYPES,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
 const NetworkLocationsModule = require("../modules/network-locations");
@@ -122,26 +121,19 @@ function scanUSBRuntimes() {
     }
 
     dispatch({ type: USB_RUNTIMES_SCAN_START });
     await refreshUSBRuntimes();
     dispatch({ type: USB_RUNTIMES_SCAN_SUCCESS });
   };
 }
 
-function updateMultiE10sStatus(isMultiE10s) {
-  return (dispatch, getState) => {
-    dispatch({ type: MULTI_E10S_UPDATED, isMultiE10s});
-  };
-}
-
 module.exports = {
   addNetworkLocation,
   installAdbAddon,
   removeNetworkLocation,
   scanUSBRuntimes,
   selectPage,
   uninstallAdbAddon,
   updateAdbAddonStatus,
   updateDebugTargetCollapsibility,
-  updateMultiE10sStatus,
   updateNetworkLocations,
 };
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ServiceWorkerAction.js
@@ -6,49 +6,52 @@
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const FluentReact = require("devtools/client/shared/vendor/fluent-react");
 
+const { getCurrentRuntimeDetails } = require("../../modules/runtimes-state-helper");
+
 const InspectAction = createFactory(require("./InspectAction"));
 
 const Actions = require("../../actions/index");
 const Types = require("../../types/index");
 
 /**
  * This component displays buttons for service worker.
  */
 class ServiceWorkerAction extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       // Provided by wrapping the component with FluentReact.withLocalization.
       getString: PropTypes.func.isRequired,
       // Provided by redux state
-      isMultiE10s: PropTypes.bool.isRequired,
+      runtimeDetails: Types.runtimeDetails.isRequired,
       target: Types.debugTarget.isRequired,
     };
   }
 
   push() {
     const { dispatch, target } = this.props;
     dispatch(Actions.pushServiceWorker(target.id));
   }
 
   start() {
     const { dispatch, target } = this.props;
     dispatch(Actions.startServiceWorker(target.details.registrationActor));
   }
 
   _renderAction() {
-    const { dispatch, isMultiE10s, target } = this.props;
+    const { dispatch, runtimeDetails, target } = this.props;
     const { isActive, isRunning } = target.details;
+    const { isMultiE10s } = runtimeDetails;
 
     if (!isRunning) {
       const startLabel = this.props.getString("about-debugging-worker-action-start");
       return this._renderButton({
         className: "default-button",
         disabled: isMultiE10s,
         label: startLabel,
         onClick: this.start.bind(this),
@@ -90,14 +93,14 @@ class ServiceWorkerAction extends PureCo
       },
       this._renderAction()
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
-    isMultiE10s: state.ui.isMultiE10s,
+    runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
   };
 };
 
 module.exports = FluentReact.withLocalization(
   connect(mapStateToProps)(ServiceWorkerAction));
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -14,17 +14,16 @@ const actionTypes = {
   ADB_ADDON_STATUS_UPDATED: "ADB_ADDON_STATUS_UPDATED",
   CONNECT_RUNTIME_FAILURE: "CONNECT_RUNTIME_FAILURE",
   CONNECT_RUNTIME_START: "CONNECT_RUNTIME_START",
   CONNECT_RUNTIME_SUCCESS: "CONNECT_RUNTIME_SUCCESS",
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED: "DEBUG_TARGET_COLLAPSIBILITY_UPDATED",
   DISCONNECT_RUNTIME_FAILURE: "DISCONNECT_RUNTIME_FAILURE",
   DISCONNECT_RUNTIME_START: "DISCONNECT_RUNTIME_START",
   DISCONNECT_RUNTIME_SUCCESS: "DISCONNECT_RUNTIME_SUCCESS",
-  MULTI_E10S_UPDATED: "MULTI_E10S_UPDATED",
   NETWORK_LOCATIONS_UPDATED: "NETWORK_LOCATIONS_UPDATED",
   PAGE_SELECTED: "PAGE_SELECTED",
   REMOTE_RUNTIMES_UPDATED: "REMOTE_RUNTIMES_UPDATED",
   REQUEST_EXTENSIONS_FAILURE: "REQUEST_EXTENSIONS_FAILURE",
   REQUEST_EXTENSIONS_START: "REQUEST_EXTENSIONS_START",
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
   REQUEST_TABS_START: "REQUEST_TABS_START",
@@ -36,16 +35,19 @@ const actionTypes = {
   TEMPORARY_EXTENSION_INSTALL_START: "TEMPORARY_EXTENSION_INSTALL_START",
   TEMPORARY_EXTENSION_INSTALL_SUCCESS: "TEMPORARY_EXTENSION_INSTALL_SUCCESS",
   UNWATCH_RUNTIME_FAILURE: "UNWATCH_RUNTIME_FAILURE",
   UNWATCH_RUNTIME_START: "UNWATCH_RUNTIME_START",
   UNWATCH_RUNTIME_SUCCESS: "UNWATCH_RUNTIME_SUCCESS",
   UPDATE_CONNECTION_PROMPT_SETTING_FAILURE: "UPDATE_CONNECTION_PROMPT_SETTING_FAILURE",
   UPDATE_CONNECTION_PROMPT_SETTING_START: "UPDATE_CONNECTION_PROMPT_SETTING_START",
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS: "UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS",
+  UPDATE_RUNTIME_MULTIE10S_FAILURE: "UPDATE_RUNTIME_MULTIE10S_FAILURE",
+  UPDATE_RUNTIME_MULTIE10S_START: "UPDATE_RUNTIME_MULTIE10S_START",
+  UPDATE_RUNTIME_MULTIE10S_SUCCESS: "UPDATE_RUNTIME_MULTIE10S_SUCCESS",
   USB_RUNTIMES_SCAN_START: "USB_RUNTIMES_SCAN_START",
   USB_RUNTIMES_SCAN_SUCCESS: "USB_RUNTIMES_SCAN_SUCCESS",
   WATCH_RUNTIME_FAILURE: "WATCH_RUNTIME_FAILURE",
   WATCH_RUNTIME_START: "WATCH_RUNTIME_START",
   WATCH_RUNTIME_SUCCESS: "WATCH_RUNTIME_SUCCESS",
 };
 
 const DEBUG_TARGETS = {
--- a/devtools/client/aboutdebugging-new/src/create-store.js
+++ b/devtools/client/aboutdebugging-new/src/create-store.js
@@ -4,17 +4,16 @@
 
 "use strict";
 
 const Services = require("Services");
 
 const { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
 const { thunk } = require("devtools/client/shared/redux/middleware/thunk.js");
 const { waitUntilService } = require("devtools/client/shared/redux/middleware/wait-service.js");
-const { isMultiE10s } = require("devtools/client/shared/multi-e10s-helper");
 
 const rootReducer = require("./reducers/index");
 const { DebugTargetsState } = require("./reducers/debug-targets-state");
 const { RuntimesState } = require("./reducers/runtimes-state");
 const { UiState } = require("./reducers/ui-state");
 const debugTargetListenerMiddleware = require("./middleware/debug-target-listener");
 const errorLoggingMiddleware = require("./middleware/error-logging");
 const extensionComponentDataMiddleware = require("./middleware/extension-component-data");
@@ -46,12 +45,12 @@ function configureStore() {
 function getUiState() {
   const collapsibilities = getDebugTargetCollapsibilities();
   const locations = getNetworkLocations();
   const networkEnabled = Services.prefs.getBoolPref(PREFERENCES.NETWORK_ENABLED, false);
   const wifiEnabled = Services.prefs.getBoolPref(PREFERENCES.WIFI_ENABLED, false);
   const showSystemAddons = Services.prefs.getBoolPref(PREFERENCES.SHOW_SYSTEM_ADDONS,
     false);
   return new UiState(locations, collapsibilities, networkEnabled, wifiEnabled,
-    showSystemAddons, isMultiE10s());
+    showSystemAddons);
 }
 
 exports.configureStore = configureStore;
--- a/devtools/client/aboutdebugging-new/src/modules/client-wrapper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/client-wrapper.js
@@ -54,28 +54,33 @@ class ClientWrapper {
   removeListener(evt, listener) {
     if (MAIN_ROOT_EVENTS.includes(evt)) {
       this.client.mainRoot.off(evt, listener);
     } else {
       this.client.removeListener(evt, listener);
     }
   }
 
+  async getFront(typeName) {
+    return this.client.mainRoot.getFront(typeName);
+  }
+
   onFront(typeName, listener) {
     this.client.mainRoot.onFront(typeName, listener);
   }
 
   async getDeviceDescription() {
-    const deviceFront = await this.client.mainRoot.getFront("device");
-    const { brandName, channel, deviceName, version } =
+    const deviceFront = await this.getFront("device");
+    const { brandName, channel, deviceName, isMultiE10s, version } =
       await deviceFront.getDescription();
     // Only expose a specific set of properties.
     return {
       channel,
       deviceName,
+      isMultiE10s,
       name: brandName,
       version,
     };
   }
 
   async setPreference(prefName, value) {
     const prefType = PREF_TO_TYPE[prefName];
     const preferenceFront = await this.client.mainRoot.getFront("preference");
--- a/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
+++ b/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
@@ -9,24 +9,21 @@ const { DEBUG_TARGETS, DEBUG_TARGET_PANE
 const ALL_DEBUG_TARGETS = [
   DEBUG_TARGETS.EXTENSION,
   DEBUG_TARGETS.TAB,
   DEBUG_TARGETS.WORKER,
 ];
 
 const SUPPORTED_TARGET_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: ALL_DEBUG_TARGETS,
+  [RUNTIMES.NETWORK]: ALL_DEBUG_TARGETS,
   [RUNTIMES.USB]: [
     DEBUG_TARGETS.EXTENSION,
     DEBUG_TARGETS.TAB,
   ],
-  [RUNTIMES.NETWORK]: [
-    DEBUG_TARGETS.EXTENSION,
-    DEBUG_TARGETS.TAB,
-  ],
 };
 
 function isSupportedDebugTarget(runtimeType, debugTargetType) {
   return SUPPORTED_TARGET_BY_RUNTIME[runtimeType].includes(debugTargetType);
 }
 exports.isSupportedDebugTarget = isSupportedDebugTarget;
 
 const ALL_DEBUG_TARGET_PANES = [
@@ -39,20 +36,19 @@ const ALL_DEBUG_TARGET_PANES = [
 ];
 
 const SUPPORTED_TARGET_PANE_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: ALL_DEBUG_TARGET_PANES,
   [RUNTIMES.USB]: [
     DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
     DEBUG_TARGET_PANE.TAB,
   ],
-  [RUNTIMES.NETWORK]: [
-    DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
-    DEBUG_TARGET_PANE.TAB,
-  ],
+  // All debug target panes except temporary extensions
+  [RUNTIMES.NETWORK]: ALL_DEBUG_TARGET_PANES.filter(p =>
+    p !== DEBUG_TARGET_PANE.TEMPORARY_EXTENSION),
 };
 
 /**
  * A debug target pane is more specialized than a debug target. For instance EXTENSION is
  * a DEBUG_TARGET but INSTALLED_EXTENSION and TEMPORARY_EXTENSION are DEBUG_TARGET_PANES.
  */
 function isSupportedDebugTargetPane(runtimeType, debugTargetPaneKey) {
   return SUPPORTED_TARGET_PANE_BY_RUNTIME[runtimeType].includes(debugTargetPaneKey);
--- a/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
@@ -41,8 +41,9 @@ function getAllRuntimes(runtimesState) {
   ];
 }
 exports.getAllRuntimes = getAllRuntimes;
 
 function getCurrentRuntimeDetails(runtimesState) {
   const runtime = getCurrentRuntime(runtimesState);
   return runtime ? runtime.runtimeDetails : null;
 }
+exports.getCurrentRuntimeDetails = getCurrentRuntimeDetails;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_SUCCESS,
   RUNTIMES,
   UNWATCH_RUNTIME_SUCCESS,
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
+  UPDATE_RUNTIME_MULTIE10S_SUCCESS,
   REMOTE_RUNTIMES_UPDATED,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
 const {
   findRuntimeById,
 } = require("../modules/runtimes-state-helper");
 
@@ -92,16 +93,25 @@ function runtimesReducer(state = Runtime
       const { connectionPromptEnabled } = action;
       const { id: runtimeId } = action.runtime;
       const runtime = findRuntimeById(runtimeId, state);
       const runtimeDetails =
         Object.assign({}, runtime.runtimeDetails, { connectionPromptEnabled });
       return _updateRuntimeById(runtimeId, { runtimeDetails }, state);
     }
 
+    case UPDATE_RUNTIME_MULTIE10S_SUCCESS: {
+      const { isMultiE10s } = action;
+      const { id: runtimeId } = action.runtime;
+      const runtime = findRuntimeById(runtimeId, state);
+      const runtimeDetails =
+        Object.assign({}, runtime.runtimeDetails, { isMultiE10s });
+      return _updateRuntimeById(runtimeId, { runtimeDetails }, state);
+    }
+
     case REMOTE_RUNTIMES_UPDATED: {
       const { runtimes, runtimeType } = action;
       const key = TYPE_TO_RUNTIMES_KEY[runtimeType];
       return Object.assign({}, state, {
         [key]: runtimes,
       });
     }
 
--- a/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
@@ -2,32 +2,30 @@
  * 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 {
   ADB_ADDON_STATUS_UPDATED,
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
-  MULTI_E10S_UPDATED,
   NETWORK_LOCATIONS_UPDATED,
   PAGE_SELECTED,
   TEMPORARY_EXTENSION_INSTALL_FAILURE,
   TEMPORARY_EXTENSION_INSTALL_SUCCESS,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
 function UiState(locations = [], debugTargetCollapsibilities = {},
                  networkEnabled = false, wifiEnabled = false,
-                 showSystemAddons = false, isMultiE10s = false) {
+                 showSystemAddons = false) {
   return {
     adbAddonStatus: null,
     debugTargetCollapsibilities,
-    isMultiE10s,
     isScanningUsb: false,
     networkEnabled,
     networkLocations: locations,
     selectedPage: null,
     selectedRuntime: null,
     showSystemAddons,
     temporaryInstallError: null,
     wifiEnabled,
@@ -43,21 +41,16 @@ function uiReducer(state = UiState(), ac
 
     case DEBUG_TARGET_COLLAPSIBILITY_UPDATED: {
       const { isCollapsed, key } = action;
       const debugTargetCollapsibilities = new Map(state.debugTargetCollapsibilities);
       debugTargetCollapsibilities.set(key, isCollapsed);
       return Object.assign({}, state, { debugTargetCollapsibilities });
     }
 
-    case MULTI_E10S_UPDATED: {
-      const { isMultiE10s } = action;
-      return Object.assign({}, state, { isMultiE10s });
-    }
-
     case NETWORK_LOCATIONS_UPDATED: {
       const { locations } = action;
       return Object.assign({}, state, { networkLocations: locations });
     }
 
     case PAGE_SELECTED: {
       const { page, runtimeId } = action;
       return Object.assign({}, state,
--- a/devtools/client/aboutdebugging-new/src/types/index.js
+++ b/devtools/client/aboutdebugging-new/src/types/index.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/. */
 
 "use strict";
 
 const { debugTarget } = require("./debug-target");
-const { runtime } = require("./runtime");
+const { runtime, runtimeDetails } = require("./runtime");
 
 module.exports = Object.assign({}, {
   debugTarget,
   runtime,
+  runtimeDetails,
 });
--- a/devtools/client/aboutdebugging-new/src/types/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/types/runtime.js
@@ -26,17 +26,22 @@ const runtimeDetails = {
   // ClientWrapper built using a DebuggerClient for the runtime
   clientWrapper: PropTypes.instanceOf(ClientWrapper).isRequired,
 
   // reflect devtools.debugger.prompt-connection preference of this runtime
   connectionPromptEnabled: PropTypes.bool.isRequired,
 
   // runtime information
   info: PropTypes.shape(runtimeInfo).isRequired,
+
+  // True if this runtime supports multiple content processes
+  // This might be undefined when connecting to runtimes older than Fx 66
+  isMultiE10s: PropTypes.bool,
 };
+exports.runtimeDetails = PropTypes.shape(runtimeDetails);
 
 const networkRuntimeConnectionParameter = {
   // host name of debugger server to connect
   host: PropTypes.string.isRequired,
 
   // port number of debugger server to connect
   port: PropTypes.number.isRequired,
 };
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -55,8 +55,9 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_stop_adb.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_system_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
 [browser_aboutdebugging_temporary_addon_install_error.js]
 [browser_aboutdebugging_thisfirefox.js]
 [browser_aboutdebugging_thisfirefox_runtime_info.js]
 [browser_aboutdebugging_thisfirefox_worker_inspection.js]
+[browser_aboutdebugging_workers_remote_runtime.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_workers_remote_runtime.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-mocks.js", this);
+
+const NETWORK_RUNTIME_HOST = "localhost:6080";
+const NETWORK_RUNTIME_APP_NAME = "TestNetworkApp";
+
+const TESTS = [
+  {
+    category: "Other Workers",
+    propertyName: "otherWorkers",
+    workerName: "other/worker/script.js",
+  }, {
+    category: "Service Workers",
+    propertyName: "serviceWorkers",
+    workerName: "service/worker/script.js",
+  }, {
+    category: "Shared Workers",
+    propertyName: "sharedWorkers",
+    workerName: "shared/worker/script.js",
+  },
+];
+
+const EMPTY_WORKERS_RESPONSE = {
+  otherWorkers: [],
+  serviceWorkers: [],
+  sharedWorkers: [],
+};
+
+// Test that workers are displayed and updated for remote runtimes when expected.
+add_task(async function() {
+  const mocks = new Mocks();
+
+  const { document, tab } = await openAboutDebugging();
+
+  info("Prepare Network client mock");
+  const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
+    name: NETWORK_RUNTIME_APP_NAME,
+  });
+
+  info("Test workers in runtime page for Network client");
+  await connectToRuntime(NETWORK_RUNTIME_HOST, document);
+  await selectRuntime(NETWORK_RUNTIME_HOST, NETWORK_RUNTIME_APP_NAME, document);
+
+  for (const testData of TESTS) {
+    await testWorkerOnMockedRemoteClient(testData, networkClient, mocks.thisFirefoxClient,
+     document);
+  }
+
+  await removeTab(tab);
+});
+
+/**
+ * Check that workers are visible in the runtime page for a remote client.
+ */
+async function testWorkerOnMockedRemoteClient(testData, remoteClient, firefoxClient,
+  document) {
+  const { category, propertyName, workerName } = testData;
+  info(`Test workers for category [${category}] in remote runtime`);
+
+  const workersPane = getDebugTargetPane(category, document);
+  info("Check an empty target pane message is displayed");
+  ok(workersPane.querySelector(".js-debug-target-list-empty"),
+    "Workers list is empty");
+
+  info(`Add a worker of type [${propertyName}] to the remote client`);
+  const workers = Object.assign({}, EMPTY_WORKERS_RESPONSE, {
+    [propertyName]: [{
+      name: workerName,
+      workerTargetFront: {
+        actorID: workerName,
+      },
+    }],
+  });
+  remoteClient.listWorkers = () => workers;
+  remoteClient._eventEmitter.emit("workerListChanged");
+
+  info("Wait until the worker appears");
+  await waitUntil(() => !workersPane.querySelector(".js-debug-target-list-empty"));
+
+  const workerTarget = findDebugTargetByText(workerName, document);
+  ok(workerTarget, "Worker target appeared for the remote runtime");
+
+  // Check that the list of REMOTE workers are NOT updated when the local this-firefox
+  // emits a workerListChanged event.
+  info("Remove the worker from the remote client WITHOUT sending an event");
+  remoteClient.listWorkers = () => EMPTY_WORKERS_RESPONSE;
+
+  info("Simulate a worker update on the ThisFirefox client");
+  firefoxClient._eventEmitter.emit("workerListChanged");
+
+  // To avoid wait for a set period of time we trigger another async update, adding a new
+  // tab. We assume that if the worker update mechanism had started, it would also be done
+  // when the new tab was processed.
+  info("Wait until the tab target for 'http://some.random/url.com' appears");
+  const testTab = { outerWindowID: 0, url: "http://some.random/url.com" };
+  remoteClient.listTabs = () => [testTab];
+  remoteClient._eventEmitter.emit("tabListChanged");
+  await waitUntil(() => findDebugTargetByText("http://some.random/url.com", document));
+
+  ok(findDebugTargetByText(workerName, document),
+    "The test worker is still visible");
+
+  info("Emit `workerListChanged` on remoteClient and wait for the target list to update");
+  remoteClient._eventEmitter.emit("workerListChanged");
+  await waitUntil(() => !findDebugTargetByText(workerName, document));
+}
--- a/devtools/client/aboutdebugging-new/test/browser/mocks/helper-client-wrapper-mock.js
+++ b/devtools/client/aboutdebugging-new/test/browser/mocks/helper-client-wrapper-mock.js
@@ -71,16 +71,18 @@ function createClientMock() {
     listTabs: () => [],
     // Empty arrays of workers
     listWorkers: () => ({
       otherWorkers: [],
       serviceWorkers: [],
       sharedWorkers: [],
     }),
     // no-op
+    getFront: () => {},
+    // no-op
     onFront: () => {},
     // stores the preference locally (doesn't update about:config)
     setPreference: function(prefName, value) {
       this._preferences[prefName] = value;
     },
   };
 }
 
--- a/devtools/client/aboutdebugging/components/workers/Panel.js
+++ b/devtools/client/aboutdebugging/components/workers/Panel.js
@@ -10,17 +10,17 @@ loader.lazyImporter(this, "PrivateBrowsi
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const Services = require("Services");
 const {
   addMultiE10sListener,
   isMultiE10s,
   removeMultiE10sListener,
-} = require("devtools/client/shared/multi-e10s-helper");
+} = require("devtools/shared/multi-e10s-helper");
 
 const PanelHeader = createFactory(require("../PanelHeader"));
 const TargetList = createFactory(require("../TargetList"));
 const WorkerTarget = createFactory(require("./Target"));
 const MultiE10SWarning = createFactory(require("./MultiE10sWarning"));
 const ServiceWorkerTarget = createFactory(require("./ServiceWorkerTarget"));
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
--- a/devtools/client/responsive.html/setting-onboarding-tooltip.js
+++ b/devtools/client/responsive.html/setting-onboarding-tooltip.js
@@ -15,17 +15,20 @@ const CONTAINER_WIDTH = 270;
 
 /**
  * Setting onboarding tooltip that is shown on the setting menu button in the RDM toolbar
  * when the pref is on.
  */
 class SettingOnboardingTooltip {
   constructor(doc) {
     this.doc = doc;
-    this.tooltip = new HTMLTooltip(this.doc, { type: "arrow" });
+    this.tooltip = new HTMLTooltip(this.doc, {
+      consumeOutsideClicks: false,
+      type: "arrow",
+    });
 
     this.onCloseButtonClick = this.onCloseButtonClick.bind(this);
 
     const container = doc.createElement("div");
     container.className = "onboarding-container";
 
     const icon = doc.createElement("span");
     icon.className = "onboarding-icon";
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -32,17 +32,16 @@ DevToolsModules(
     'enum.js',
     'file-saver.js',
     'focus.js',
     'getjson.js',
     'inplace-editor.js',
     'key-shortcuts.js',
     'keycodes.js',
     'link.js',
-    'multi-e10s-helper.js',
     'natural-sort.js',
     'node-attribute-parser.js',
     'options-view.js',
     'output-parser.js',
     'poller.js',
     'prefs.js',
     'react-utils.js',
     'scroll.js',
--- a/devtools/server/actors/device.js
+++ b/devtools/server/actors/device.js
@@ -3,43 +3,57 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Ci, Cc} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
+const {
+  addMultiE10sListener,
+  isMultiE10s,
+  removeMultiE10sListener,
+} = require("devtools/shared/multi-e10s-helper");
+
 const {DebuggerServer} = require("devtools/server/main");
 const {getSystemInfo} = require("devtools/shared/system");
 const {deviceSpec} = require("devtools/shared/specs/device");
 const {AppConstants} = require("resource://gre/modules/AppConstants.jsm");
 
 exports.DeviceActor = protocol.ActorClassWithSpec(deviceSpec, {
   initialize: function(conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     // pageshow and pagehide event release wake lock, so we have to acquire
     // wake lock again by pageshow event
     this._onPageShow = this._onPageShow.bind(this);
     if (this._window) {
       this._window.addEventListener("pageshow", this._onPageShow, true);
     }
     this._acquireWakeLock();
+
+    this._onMultiE10sUpdated = this._onMultiE10sUpdated.bind(this);
+    addMultiE10sListener(this._onMultiE10sUpdated);
   },
 
   destroy: function() {
     protocol.Actor.prototype.destroy.call(this);
     this._releaseWakeLock();
     if (this._window) {
       this._window.removeEventListener("pageshow", this._onPageShow, true);
     }
+    removeMultiE10sListener(this._onMultiE10sUpdated);
+  },
+
+  _onMultiE10sUpdated: function() {
+    this.emit("multi-e10s-updated", isMultiE10s());
   },
 
   getDescription: function() {
-    return getSystemInfo();
+    return Object.assign({}, getSystemInfo(), { isMultiE10s: isMultiE10s() });
   },
 
   screenshotToDataURL: function() {
     const window = this._window;
     const { devicePixelRatio } = window;
     const canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     const width = window.innerWidth;
     const height = window.innerHeight;
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -60,16 +60,17 @@ DevToolsModules(
     'extend.js',
     'flags.js',
     'generate-uuid.js',
     'indentation.js',
     'indexed-db.js',
     'l10n.js',
     'loader-plugin-raw.jsm',
     'Loader.jsm',
+    'multi-e10s-helper.js',
     'Parser.jsm',
     'path.js',
     'plural-form.js',
     'protocol.js',
     'system.js',
     'task.js',
     'ThreadSafeDevToolsUtils.js',
     'throttle.js',
rename from devtools/client/shared/multi-e10s-helper.js
rename to devtools/shared/multi-e10s-helper.js
--- a/devtools/shared/specs/device.js
+++ b/devtools/shared/specs/device.js
@@ -1,17 +1,23 @@
 /* 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 {RetVal, generateActorSpec} = require("devtools/shared/protocol");
-
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
 const deviceSpec = generateActorSpec({
   typeName: "device",
 
+  events: {
+    "multi-e10s-updated": {
+      type: "multi-e10s-updated",
+      isMultiE10s: Arg(0, "boolean"),
+    },
+  },
+
   methods: {
     getDescription: {request: {}, response: { value: RetVal("json")}},
     screenshotToDataURL: {request: {}, response: { value: RetVal("longstring")}},
   },
 });
 
 exports.deviceSpec = deviceSpec;
--- a/devtools/shared/system.js
+++ b/devtools/shared/system.js
@@ -30,17 +30,17 @@ const APP_MAP = {
   "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "thunderbird",
   "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "seamonkey",
   "{718e30fb-e89b-41dd-9da7-e25a45638b28}": "sunbird",
   "{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "mobile/android",
 };
 
 var CACHED_INFO = null;
 
-async function getSystemInfo() {
+function getSystemInfo() {
   if (CACHED_INFO) {
     return CACHED_INFO;
   }
 
   const appInfo = Services.appinfo;
   const win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
   const [processor, compiler] = appInfo.XPCOMABI.split("-");
   let dpi,
deleted file mode 100644
--- a/editor/reftests/xul/emptytextbox-3.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-1.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-2.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number" hidespinbuttons="false"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-3.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number" hidespinbuttons="true"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-4.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number" value="1" hidespinbuttons="true"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-5.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number" value="test" hidespinbuttons="true"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/number-ref.xul
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="resource://reftest/input.css" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <html:input value="0" class="num"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/numberwithvalue-1.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <textbox type="number" value="123" hidespinbuttons="true"/>
-      
-</window>
deleted file mode 100644
--- a/editor/reftests/xul/numberwithvalue-ref.xul
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="resource://reftest/input.css" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="Textbox tests">
-
-  <script type="text/javascript" src="platform.js"/>
-
-  <html:input value="123" class="num"/>
-      
-</window>
--- a/editor/reftests/xul/reftest.list
+++ b/editor/reftests/xul/reftest.list
@@ -3,26 +3,17 @@ fails-if(Android) skip-if(winWidget) == 
 # There is no way to simulate an autocomplete textbox in windows XP/Vista/7/8/10 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == autocomplete-1.xul autocomplete-ref.xul # bug 783658
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == emptyautocomplete-1.xul emptyautocomplete-ref.xul # bug 783658
 != emptymultiline-1.xul emptymultiline-ref.xul
 fails-if(Android) == emptymultiline-2.xul emptymultiline-ref.xul # bug 783658
 fails-if(Android) skip-if(winWidget) == emptytextbox-1.xul emptytextbox-ref.xul # Windows: bug 1239170
 fails-if(Android) skip-if(winWidget) == emptytextbox-2.xul emptytextbox-ref.xul # Windows: bug 1239170
-!= emptytextbox-3.xul emptytextbox-ref.xul
 != emptytextbox-4.xul emptytextbox-ref.xul
-# There is no way to simulate a number textbox in windows XP/Vista/7 default theme using CSS.
-# Therefore, the equlity tests below should be marked as failing.
-!= number-1.xul number-ref.xul
-!= number-2.xul number-ref.xul
-fuzzy-if(webrender,0-205,0-7) fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == number-3.xul number-ref.xul # bug 783658
-!= number-4.xul number-ref.xul
-fuzzy-if(webrender,0-205,0-7) fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == number-5.xul number-ref.xul # bug 783658
-fuzzy-if(webrender,0-255,0-7) fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == numberwithvalue-1.xul numberwithvalue-ref.xul # bug 783658
 fails-if(Android) skip-if(winWidget) == passwd-1.xul passwd-ref.xul # Windows: bug 1239170
 fails-if(Android) skip-if(winWidget) == passwd-2.xul passwd-ref.xul # Windows: bug 1239170
 != passwd-3.xul passwd-ref.xul
 fails-if(Android) == plain-1.xul plain-ref.xul # bug 783658
 fails-if(Android) skip-if(winWidget) == textbox-1.xul textbox-ref.xul # Windows: bug 1239170
 != textbox-disabled.xul textbox-ref.xul
 # Read-only textboxes look like normal textboxes in windows Vista/7 default theme
 fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(6\.[012]|10\.0)/.test(http.oscpu)) skip-if(winWidget) != textbox-readonly.xul textbox-ref.xul # Windows: bug 1239170
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -999,16 +999,19 @@ bool AsyncCompositionManager::ApplyAsync
                     if (animator) {
                       animator->UpdateRootFrameMetrics(metrics);
                       animator->FirstPaint();
                     }
                     LayersId rootLayerTreeId = bridge->RootLayerTreeId();
                     if (RefPtr<UiCompositorControllerParent> uiController =
                             UiCompositorControllerParent::
                                 GetFromRootLayerTreeId(rootLayerTreeId)) {
+                      if (!animator) {
+                        uiController->NotifyUpdateScreenMetrics(metrics);
+                      }
                       uiController->NotifyFirstPaint();
                     }
                     mIsFirstPaint = false;
                   }
                   if (mLayersUpdated) {
                     LayersId rootLayerTreeId = bridge->RootLayerTreeId();
                     if (RefPtr<UiCompositorControllerParent> uiController =
                             UiCompositorControllerParent::
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -11,16 +11,18 @@
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 
+#include "FrameMetrics.h"
+
 namespace mozilla {
 namespace layers {
 
 typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
 
 /* static */ RefPtr<UiCompositorControllerParent>
 UiCompositorControllerParent::GetFromRootLayerTreeId(
     const LayersId& aRootLayerTreeId) {
@@ -235,16 +237,31 @@ void UiCompositorControllerParent::Notif
   }
 #endif
 }
 
 void UiCompositorControllerParent::NotifyFirstPaint() {
   ToolbarAnimatorMessageFromCompositor(FIRST_PAINT);
 }
 
+void UiCompositorControllerParent::NotifyUpdateScreenMetrics(
+    const FrameMetrics& aMetrics) {
+#if defined(MOZ_WIDGET_ANDROID)
+  CSSToScreenScale scale = ViewTargetAs<ScreenPixel>(
+      aMetrics.GetZoom().ToScaleFactor(),
+      PixelCastJustification::ScreenIsParentLayerForRoot);
+  ScreenPoint scrollOffset = aMetrics.GetScrollOffset() * scale;
+  CompositorThreadHolder::Loop()->PostTask(
+      NewRunnableMethod<ScreenPoint, CSSToScreenScale>(
+          "UiCompositorControllerParent::SendRootFrameMetrics", this,
+          &UiCompositorControllerParent::SendRootFrameMetrics, scrollOffset,
+          scale));
+#endif
+}
+
 UiCompositorControllerParent::UiCompositorControllerParent(
     const LayersId& aRootLayerTreeId)
     : mRootLayerTreeId(aRootLayerTreeId)
 #ifdef MOZ_WIDGET_ANDROID
       ,
       mCompositorLayersUpdateEnabled(false)
 #endif
       ,
--- a/gfx/layers/ipc/UiCompositorControllerParent.h
+++ b/gfx/layers/ipc/UiCompositorControllerParent.h
@@ -11,16 +11,18 @@
 #include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif  // defined(MOZ_WIDGET_ANDROID)
 #include "mozilla/ipc/Shmem.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 namespace layers {
 
+struct FrameMetrics;
+
 class UiCompositorControllerParent final
     : public PUiCompositorControllerParent {
   // UiCompositorControllerChild needs to call the private constructor when
   // running in process.
   friend class UiCompositorControllerChild;
 
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UiCompositorControllerParent)
@@ -61,16 +63,17 @@ class UiCompositorControllerParent final
 #endif  // MOZ_WIDGET_ANDROID
   void ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
   bool AllocPixelBuffer(const int32_t aSize, Shmem* aMem);
 
   // Called when a layer has been updated so the UI thread may be notified if
   // necessary.
   void NotifyLayersUpdated();
   void NotifyFirstPaint();
+  void NotifyUpdateScreenMetrics(const FrameMetrics& aMetrics);
 
  private:
   explicit UiCompositorControllerParent(const LayersId& aRootLayerTreeId);
   ~UiCompositorControllerParent();
   void InitializeForSameProcess();
   void InitializeForOutOfProcess();
   void Initialize();
   void Open(Endpoint<PUiCompositorControllerParent>&& aEndpoint);
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-fb4b9342aa1b047ac46b89cb7a70987816bee686
+ff9940fdf95412a3e5d1a70c78defba6e919c170
--- a/gfx/wr/webrender/src/freelist.rs
+++ b/gfx/wr/webrender/src/freelist.rs
@@ -258,12 +258,13 @@ impl<T, M> FreeList<T, M> {
         self.active_count -= 1;
         let slot = &mut self.slots[id.index as usize];
         slot.next = self.free_list_head;
         slot.epoch = Epoch(slot.epoch.0 + 1);
         self.free_list_head = Some(id.index);
         slot.value.take().unwrap()
     }
 
+    #[allow(dead_code)]
     pub fn len(&self) -> usize {
         self.active_count
     }
 }
--- a/gfx/wr/webrender/src/texture_cache.rs
+++ b/gfx/wr/webrender/src/texture_cache.rs
@@ -521,42 +521,51 @@ impl TextureCache {
         cache.begin_frame(now);
         cache
     }
 
     pub fn set_debug_flags(&mut self, flags: DebugFlags) {
         self.debug_flags = flags;
     }
 
-    pub fn clear(&mut self) {
+    /// Clear all standalone textures in the cache.
+    pub fn clear_standalone(&mut self) {
         let standalone_entry_handles = mem::replace(
             &mut self.handles.standalone,
             Vec::new(),
         );
 
         for handle in standalone_entry_handles {
             let entry = self.entries.free(handle);
             entry.evict();
             self.free(entry);
         }
+    }
 
+    /// Clear all shared textures in the cache.
+    pub fn clear_shared(&mut self) {
         let shared_entry_handles = mem::replace(
             &mut self.handles.shared,
             Vec::new(),
         );
 
         for handle in shared_entry_handles {
             let entry = self.entries.free(handle);
             entry.evict();
             self.free(entry);
         }
 
-        assert!(self.entries.len() == 0);
+        self.shared_textures.clear(&mut self.pending_updates);
+    }
 
-        self.shared_textures.clear(&mut self.pending_updates);
+    /// Clear all entries in the texture cache. This is a fairly drastic
+    /// step that should only be called very rarely.
+    pub fn clear(&mut self) {
+        self.clear_standalone();
+        self.clear_shared();
     }
 
     /// Called at the beginning of each frame.
     pub fn begin_frame(&mut self, stamp: FrameStamp) {
         self.now = stamp;
         self.maybe_reclaim_shared_cache_memory();
     }
 
@@ -592,17 +601,17 @@ impl TextureCache {
         if self.shared_textures.empty_region_bytes() >= RECLAIM_THRESHOLD_BYTES {
             self.reached_reclaim_threshold.get_or_insert(self.now.time());
         } else {
             self.reached_reclaim_threshold = None;
         }
         if let Some(t) = self.reached_reclaim_threshold {
             let dur = self.now.time().duration_since(t).unwrap_or(Duration::default());
             if dur >= Duration::from_secs(5) {
-                self.clear();
+                self.clear_shared();
                 self.reached_reclaim_threshold = None;
             }
         }
 
     }
 
     pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
         // Expire standalone entries.
--- a/mobile/android/installer/allowed-dupes.mn
+++ b/mobile/android/installer/allowed-dupes.mn
@@ -9,17 +9,17 @@ chrome/toolkit/skin/classic/global/autoc
 chrome/toolkit/skin/classic/global/button.css
 chrome/toolkit/skin/classic/global/checkbox.css
 chrome/toolkit/skin/classic/global/dialog.css
 chrome/toolkit/skin/classic/global/dropmarker.css
 chrome/toolkit/skin/classic/global/global.css
 chrome/toolkit/skin/classic/global/listbox.css
 chrome/toolkit/skin/classic/global/menu.css
 chrome/toolkit/skin/classic/global/menulist.css
-chrome/toolkit/skin/classic/global/numberbox.css
+chrome/toolkit/skin/classic/global/numberinput.css
 chrome/toolkit/skin/classic/global/popup.css
 chrome/toolkit/skin/classic/global/preferences.css
 chrome/toolkit/skin/classic/global/radio.css
 chrome/toolkit/skin/classic/global/richlistbox.css
 chrome/toolkit/skin/classic/global/scrollbars.css
 chrome/toolkit/skin/classic/global/scrollbox.css
 chrome/toolkit/skin/classic/global/splitter.css
 chrome/toolkit/skin/classic/global/tabbox.css
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -10,17 +10,16 @@ import urllib
 
 from marionette_driver import By, errors, expected, Wait
 from marionette_driver.keys import Keys
 from marionette_driver.marionette import Alert
 from marionette_harness import (
     MarionetteTestCase,
     run_if_e10s,
     run_if_manage_instance,
-    skip,
     skip_if_mobile,
     WindowManagerMixin,
 )
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' # noqa
@@ -814,20 +813,19 @@ class TestPageLoadStrategy(BaseNavigatio
         self.marionette.delete_session()
         self.marionette.start_session({"pageLoadStrategy": "normal"})
 
         self.marionette.navigate(self.test_page_slow_resource)
         self.assertEqual(self.test_page_slow_resource, self.marionette.get_url())
         self.assertEqual("complete", self.ready_state)
         self.marionette.find_element(By.ID, "slow")
 
-    @skip("Bug 1422741 - Causes following tests to fail in loading remote browser")
     @run_if_e10s("Requires e10s mode enabled")
     def test_strategy_after_remoteness_change(self):
-        """Bug 1378191 - Reset of capabilities after listener reload"""
+        """Bug 1378191 - Reset of capabilities after listener reload."""
         self.marionette.delete_session()
         self.marionette.start_session({"pageLoadStrategy": "eager"})
 
         # Trigger a remoteness change which will reload the listener script
         self.assertTrue(self.is_remote_tab, "Initial tab doesn't have remoteness flag set")
         self.marionette.navigate("about:robots")
         self.assertFalse(self.is_remote_tab, "Tab has remoteness flag set")
         self.marionette.navigate(self.test_page_slow_resource)
--- a/testing/marionette/reftest.js
+++ b/testing/marionette/reftest.js
@@ -403,16 +403,24 @@ max-width: ${REFTEST_WIDTH}px; max-heigh
     logger.info(`Allowed ${allowedPixels.join("-")} pixels different, ` +
                 `maximum difference per channel ${allowedDiff.join("-")}`);
     return ((maxDifference >= allowedDiff[0] &&
              maxDifference <= allowedDiff[1]) &&
             (pixelsDifferent >= allowedPixels[0] ||
              pixelsDifferent <= allowedPixels[1]));
   }
 
+  ensureFocus(win) {
+    const focusManager = Services.focus;
+    if (focusManager.activeWindow != win) {
+      focusManager.activeWindow = win;
+    }
+    this.driver.curBrowser.contentBrowser.focus();
+  }
+
   async screenshot(win, url, timeout) {
     win.innerWidth = REFTEST_WIDTH;
     win.innerHeight = REFTEST_HEIGHT;
 
     // On windows the above doesn't *actually* set the window to be the
     // reftest size; but *does* set the content area to be the right size;
     // the window is given some extra borders that aren't explicable from CSS
     let browserRect = win.gBrowser.getBoundingClientRect();
@@ -461,17 +469,17 @@ max-width: ${REFTEST_WIDTH}px; max-heigh
         await this.driver.listener.refresh(navigateOpts);
       } else {
         navigateOpts.url = url;
         navigateOpts.loadEventExpected = false;
         await this.driver.listener.get(navigateOpts);
         this.lastURL = url;
       }
 
-      this.driver.curBrowser.contentBrowser.focus();
+      this.ensureFocus(win);
       await this.driver.listener.reftestWait(url, this.remote);
 
       canvas = capture.canvas(
           win,
           0, // left
           0, // top
           browserRect.width,
           browserRect.height,
--- a/toolkit/components/printing/content/printPreviewToolbar.js
+++ b/toolkit/components/printing/content/printPreviewToolbar.js
@@ -19,17 +19,17 @@ customElements.define("printpreview-tool
       <button id="print-preview-print" label="&print.label;" accesskey="&print.accesskey;" oncommand="this.parentNode.print();"/>
       <button id="print-preview-pageSetup" label="&pageSetup.label;" accesskey="&pageSetup.accesskey;" oncommand="this.parentNode.doPageSetup();"/>
       <vbox align="center" pack="center">
         <label value="&page.label;" accesskey="&page.accesskey;" control="print-preview-pageNumber"/>
       </vbox>
       <toolbarbutton id="print-preview-navigateHome" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"/>
       <toolbarbutton id="print-preview-navigatePrevious" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"/>
       <hbox align="center" pack="center">
-        <textbox id="print-preview-pageNumber" value="1" min="1" type="number" hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"/>
+        <html:input id="print-preview-pageNumber" hidespinbuttons="true" type="number" value="1" min="1"/>
         <label value="&of.label;"/>
         <label id="print-preview-totalPages" value="1"/>
       </hbox>
       <toolbarbutton id="print-preview-navigateNext" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"/>
       <toolbarbutton id="print-preview-navigateEnd" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"/>
       <toolbarseparator class="toolbarseparator-primary"/>
       <vbox align="center" pack="center">
         <label id="print-preview-scale-label" value="&scale.label;" accesskey="&scale.accesskey;" control="print-preview-scale"/>
@@ -99,16 +99,21 @@ customElements.define("printpreview-tool
 
     this.mPrintPreviewObs = "";
 
     this.mWebProgress = "";
 
     this.mPPBrowser = null;
 
     this.mMessageManager = null;
+
+    this.mOnPageTextBoxChange = () => {
+      this.navigate(0, Number(this.mPageTextBox.value), 0);
+    };
+    this.mPageTextBox.addEventListener("change", this.mOnPageTextBoxChange);
   }
 
   initialize(aPPBrowser) {
     let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
     if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
       this.mSimplifyPageCheckbox.hidden = true;
       this.mSimplifyPageToolbarSeparator.hidden = true;
     }
@@ -135,16 +140,17 @@ customElements.define("printpreview-tool
       this.mMessageManager.removeMessageListener("Printing:Preview:UpdatePageCount", this);
       delete this.mMessageManager;
       delete this.mPPBrowser;
     }
   }
 
   disconnectedCallback() {
     window.removeEventListener("unload", this.disconnectedCallback);
+    this.mPageTextBox.removeEventListener("change", this.mOnPageTextBoxChange);
     this.destroy();
   }
 
   disableUpdateTriggers(aDisabled) {
     this.mPrintButton.disabled = aDisabled;
     this.mPageSetupButton.disabled = aDisabled;
     this.mNavigateHomeButton.disabled = aDisabled;
     this.mNavigatePreviousButton.disabled = aDisabled;
@@ -183,19 +189,19 @@ customElements.define("printpreview-tool
       } else {
         navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
         this.mPageTextBox.value = this.mPageTextBox.max;
       }
       pageNum = 0;
     } else if (aDirection) {
       // aDirection is either +1 or -1, and allows us to increment
       // or decrement our currently viewed page.
-      this.mPageTextBox.valueNumber += aDirection;
+      this.mPageTextBox.value = Number(this.mPageTextBox.value) + aDirection;
       navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
-      pageNum = this.mPageTextBox.value; // TODO: back to valueNumber?
+      pageNum = this.mPageTextBox.value;
     } else {
       // We're going to a specific page (aPageNum)
       navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
       pageNum = aPageNum;
     }
 
     this.mMessageManager.sendAsyncMessage("Printing:Preview:Navigate", {
       navType,
--- a/toolkit/components/telemetry/tests/moz.build
+++ b/toolkit/components/telemetry/tests/moz.build
@@ -9,16 +9,20 @@ DIST_INSTALL = False
 SOURCES += [
     'modules-test.cpp',
 ]
 
 SharedLibrary('modules-test')
 
 NO_PGO = True
 
+TESTING_JS_MODULES += [
+    'utils/TelemetryTestUtils.jsm',
+]
+
 if CONFIG['COMPILE_ENVIRONMENT']:
     shared_library = '!%smodules-test%s' % (CONFIG['DLL_PREFIX'], CONFIG['DLL_SUFFIX'])
     TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += [shared_library]
 
 if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += [
         'untrusted-startup-test-dll',
     ]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/utils/TelemetryTestUtils.jsm
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["TelemetryTestUtils"];
+
+ChromeUtils.import("resource://testing-common/Assert.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var TelemetryTestUtils = {
+  /* Scalars */
+
+  /**
+   * An helper that asserts the value of a scalar if it's expected to be > 0,
+   * otherwise makes sure that the scalar has not been reported.
+   *
+   * @param {Object} scalars The snapshot of the scalars.
+   * @param {String} scalarName The name of the scalar to check.
+   * @param {Number} value The expected value for the provided scalar.
+   * @param {String} msg The message to print when checking the value.
+   */
+  assertScalar(scalars, scalarName, value, msg) {
+    if (value > 0) {
+      Assert.equal(scalars[scalarName], value, msg);
+      return;
+    }
+    Assert.ok(!(scalarName in scalars), scalarName + " must not be reported.");
+  },
+
+  /**
+   * Asserts if the snapshotted keyed scalars contain the expected
+   * data.
+   *
+   * @param {Object} scalars The snapshot of the keyed scalars.
+   * @param {String} scalarName The name of the keyed scalar to check.
+   * @param {String} key The key that must be within the keyed scalar.
+   * @param {String|Boolean|Number} expectedValue The expected value for the
+   *        provided key in the scalar.
+   */
+  assertKeyedScalar(scalars, scalarName, key, expectedValue) {
+    Assert.ok(scalarName in scalars,
+              scalarName + " must be recorded.");
+    Assert.ok(key in scalars[scalarName],
+              scalarName + " must contain the '" + key + "' key.");
+    Assert.equal(scalars[scalarName][key], expectedValue,
+              scalarName + "['" + key + "'] must contain the expected value");
+  },
+
+  /**
+   * Returns a snapshot of scalars from the parent-process.
+   *
+   * @param {Number} aChannel The channel dataset type from nsITelemetry.
+   * @param {boolean} [aKeyed] Set to true if keyed scalars rather than normal
+   *   scalars should be snapshotted.
+   * @param {boolean} [aClear] Set to true to clear the scalars once the snapshot
+   *   has been obtained.
+   * @returns {Object} The snapshotted scalars from the parent process.
+   */
+  getParentProcessScalars(aChannel, aKeyed = false, aClear = false) {
+    const extended = aChannel == Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+    const currentExtended = Services.telemetry.canRecordExtended;
+    Services.telemetry.canRecordExtended = extended;
+    const scalars = aKeyed ?
+      Services.telemetry.getSnapshotForKeyedScalars("main", aClear).parent :
+      Services.telemetry.getSnapshotForScalars("main", aClear).parent;
+    Services.telemetry.canRecordExtended = currentExtended;
+    return scalars || {};
+  },
+
+  /* Events */
+
+  /**
+   * Asserts if snapshotted events telemetry match the expected values.
+   *
+   * @param {Array} events Snapshotted telemetry events to test.
+   * @param {Array} expectedEvents The expected event data.
+   */
+  assertEvents(events, expectedEvents) {
+    if (!Services.telemetry.canRecordExtended) {
+      console.log("Not asserting event telemetry - canRecordExtended is disabled.");
+      return;
+    }
+
+    Assert.equal(events.length, expectedEvents.length, "Should have matching amount of events.");
+
+    // Strip timestamps from the events for easier comparison.
+    events = events.map(e => e.slice(1));
+
+    for (let i = 0; i < events.length; ++i) {
+      Assert.deepEqual(events[i], expectedEvents[i], "Events should match.");
+    }
+  },
+
+  /* Histograms */
+
+  /**
+   * Clear and get the named histogram.
+   *
+   * @param {String} name The name of the histogram
+   * @returns {Object} The obtained histogram.
+   */
+  getAndClearHistogram(name) {
+    let histogram = Services.telemetry.getHistogramById(name);
+    histogram.clear();
+    return histogram;
+  },
+
+
+  /**
+   * Clear and get the named keyed histogram.
+   *
+   * @param {String} name The name of the keyed histogram
+   * @returns {Object} The obtained keyed histogram.
+   */
+  getAndClearKeyedHistogram(name) {
+    let histogram = Services.telemetry.getKeyedHistogramById(name);
+    histogram.clear();
+    return histogram;
+  },
+
+  /**
+   * Assert that the histogram index is the right value. It expects that
+   * other indexes are all zero.
+   *
+   * @param {Object} histogram The histogram to check.
+   * @param {Number} index The index to check against the expected value.
+   * @param {Number} expected The expected value of the index.
+   */
+  assertHistogram(histogram, index, expected) {
+    const snapshot = histogram.snapshot();
+    for (let [i, val] of Object.entries(snapshot.values)) {
+      if (i == index) {
+        Assert.equal(val, expected,
+          `expected counts should match for the histogram index ${i}`);
+      } else {
+        Assert.equal(val, 0,
+          `unexpected counts should be zero for the histogram index ${i}`);
+      }
+    }
+  },
+
+  /**
+   * Assert that a key within a keyed histogram contains the required sum.
+   *
+   * @param {Object} histogram The keyed histogram to check.
+   * @param {String} key The key to check.
+   * @param {Number} [expected] The expected sum for the key.
+   */
+  assertKeyedHistogramSum(histogram, key, expected) {
+    const snapshot = histogram.snapshot();
+    if (expected === undefined) {
+      Assert.ok(!(key in snapshot), `The histogram must not contain ${key}.`);
+      return;
+    }
+    Assert.ok(key in snapshot, `The histogram must contain ${key}.`);
+    Assert.equal(snapshot[key].sum, expected,
+      `The key ${key} must contain the expected sum.`);
+  },
+
+  /**
+   * Assert that the value of a key within a keyed histogram is the right value.
+   * It expects that other values are all zero.
+   *
+   * @param {Object} histogram The keyed histogram to check.
+   * @param {String} key The key to check.
+   * @param {Number} index The index to check against the expected value.
+   * @param {Number} [expected] The expected values for the key.
+   */
+  assertKeyedHistogramValue(histogram, key, index, expected) {
+    const snapshot = histogram.snapshot();
+    for (let [i, val] of Object.entries(snapshot[key].values)) {
+      if (i == index) {
+        Assert.equal(val, expected,
+          `expected counts should match for the histogram index ${i}`);
+      } else {
+        Assert.equal(val, 0,
+          `unexpected counts should be zero for the histogram index ${i}`);
+      }
+    }
+  },
+};
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -136,17 +136,18 @@ const MozElementMixin = Base => class Mo
     let doc = gXULDOMParser.parseFromString(`
       ${entities.length ? `<!DOCTYPE bindings [
         ${entities.reduce((preamble, url, index) => {
           return preamble + `<!ENTITY % _dtd-${index} SYSTEM "${url}">
             %_dtd-${index};
             `;
         }, "")}
       ]>` : ""}
-      <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+      <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+           xmlns:html="http://www.w3.org/1999/xhtml">
         ${str}
       </box>
     `, "application/xml");
     // The XUL/XBL parser is set to ignore all-whitespace nodes, whereas (X)HTML
     // does not do this. Most XUL code assumes that the whitespace has been
     // stripped out, so we simply remove all text nodes after using the parser.
     let nodeIterator = doc.createNodeIterator(doc, NodeFilter.SHOW_TEXT);
     let currentNode = nodeIterator.nextNode();
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -70,17 +70,16 @@ toolkit.jar:
    content/global/bindings/datepicker.js       (widgets/datepicker.js)
    content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
    content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/menu.xml            (widgets/menu.xml)
    content/global/bindings/menulist.xml        (widgets/menulist.xml)
    content/global/bindings/notification.xml    (widgets/notification.xml)
-   content/global/bindings/numberbox.xml       (widgets/numberbox.xml)
    content/global/bindings/popup.xml           (widgets/popup.xml)
    content/global/bindings/radio.xml           (widgets/radio.xml)
    content/global/bindings/richlistbox.xml     (widgets/richlistbox.xml)
    content/global/bindings/scrollbox.xml       (widgets/scrollbox.xml)
    content/global/bindings/spinner.js          (widgets/spinner.js)
 *  content/global/bindings/tabbox.xml          (widgets/tabbox.xml)
    content/global/bindings/text.xml            (widgets/text.xml)
 *  content/global/bindings/textbox.xml         (widgets/textbox.xml)
@@ -88,17 +87,17 @@ toolkit.jar:
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    content/global/bindings/toolbar.xml         (widgets/toolbar.xml)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/tree.xml            (widgets/tree.xml)
    content/global/bindings/videocontrols.xml   (widgets/videocontrols.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
    content/global/elements/datetimebox.js      (widgets/datetimebox.js)
    content/global/elements/findbar.js          (widgets/findbar.js)
-   content/global/elements/editor.js          (widgets/editor.js)
+   content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/notificationbox.js  (widgets/notificationbox.js)
    content/global/elements/pluginProblem.js    (widgets/pluginProblem.js)
    content/global/elements/radio.js            (widgets/radio.js)
    content/global/elements/richlistbox.js      (widgets/richlistbox.js)
    content/global/elements/marquee.css         (widgets/marquee.css)
    content/global/elements/marquee.js          (widgets/marquee.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -177,17 +177,16 @@ support-files = window_preferences_onsyn
 [test_screenPersistence.xul]
 [test_scrollbar.xul]
 [test_showcaret.xul]
 [test_subframe_origin.xul]
 [test_tabbox.xul]
 [test_tabindex.xul]
 [test_textbox_dictionary.xul]
 [test_textbox_emptytext.xul]
-[test_textbox_number.xul]
 [test_textbox_search.xul]
 [test_titlebar.xul]
 skip-if = os == "linux"
 [test_tooltip.xul]
 skip-if = (os == 'mac' && os_version == '10.10') || (os == 'win') # Bug 1141245, frequent timeouts on OSX 10.10, Windows
 [test_tooltip_noautohide.xul]
 [test_tree.xul]
 [test_tree_hier.xul]
deleted file mode 100644
--- a/toolkit/content/tests/chrome/test_textbox_number.xul
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
-<!--
-  XUL Widget Test for textbox type="number"
-  -->
-<window title="Textbox type='number' test" width="500" height="600"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-<hbox>
-  <textbox id="n1" type="number"/>
-  <textbox id="n2" type="number" value="10" min="5" max="15"/>
-</hbox>
-<hbox>
-  <textbox id="n4" type="number" value="-2" min="-8" max="18"/>
-  <textbox id="n5" type="number" value="-17" min="-10" max="-3"/>
-</hbox>
-<hbox>
-  <textbox id="n6" type="number" value="9" min="12" max="8"/>
-  <textbox id="n8" type="number" hidespinbuttons="true"/>
-</hbox>
-<hbox>
-  <textbox id="n9" type="number" oninput="updateInputEventCount();"/>
-</hbox>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
-
-  <!-- test code goes here -->
-  <script type="application/javascript"><![CDATA[
-SimpleTest.waitForExplicitFinish();
-
-// ---- NOTE: the numbers used in these tests are carefully chosen to avoid
-// ----       floating point rounding issues
-
-function doTests() {
-  var n1 = $("n1");
-  var n2 = $("n2");
-  var n4 = $("n4");
-  var n5 = $("n5");
-  var n6 = $("n6");
-
-  testValsMinMax(n1, "initial n1", 0, 0, Infinity);
-  testValsMinMax(n2, "initial n2", 10, 5, 15);
-  testValsMinMax(n4, "initial n4", -2, -8, 18);
-  testValsMinMax(n5, "initial n5", -10, -10, -3);
-  testValsMinMax(n6, "initial n6", 12, 12, 12);
-
-  // test changing the value
-  n1.value = "1700";
-  testVals(n1, "set value,", 1700);
-  n1.value = 1600;
-  testVals(n1, "set value int,", 1600);
-  n2.value = "2";
-  testVals(n2, "set value below min,", 5);
-  n2.value = 2;
-  testVals(n2, "set value below min int,", 5);
-  n2.value = 18;
-  testVals(n2, "set value above max,", 15);
-  n2.value = -6;
-  testVals(n2, "set value below min negative,", 5);
-  n5.value = -2;
-  testVals(n5, "set value above max positive,", -3);
-  n1.value = 4.75;
-  testVals(n1, "set value to decimal round,", 5);
-
-  // test changing the valueNumber
-  n1.valueNumber = 27;
-  testVals(n1, "set valueNumber,", 27);
-  n2.valueNumber = 1;
-  testVals(n2, "set valueNumber below min,", 5);
-  n2.valueNumber = 77;
-  testVals(n2, "set valueNumber above max,", 15);
-  n2.valueNumber = -5;
-  testVals(n2, "set valueNumber below min negative,", 5);
-  n5.valueNumber = -8;
-  n5.valueNumber = -1;
-  testVals(n5, "set valueNumber above max positive,", -3);
-  n1.value = 8.9;
-  testVals(n1, "set valueNumber to decimal round,", 9);
-
-  // test changing the min
-  n1.value = 6;
-  n1.min = 8;
-  testValsMinMax(n1, "set integer min,", 8, 8, Infinity);
-
-  // test changing the max
-  n1.value = 25;
-  n1.max = 22;
-  testValsMinMax(n1, "set integer max,", 22, 8, 22);
-
-  // check read only state
-  n1.readOnly = true;
-  n1.min = -10;
-  n1.max = 15;
-  n1.value = 12;
-  n1.inputField.focus();
-  // no events should fire and no changes should occur when the field is read only
-  synthesizeKeyExpectEvent("KEY_ArrowUp", {}, n1, "!change", "key up read only");
-  is(n1.value, "12", "key up read only value");
-  synthesizeKeyExpectEvent("KEY_ArrowDown", {}, n1, "!change", "key down read only");
-  is(n1.value, "12", "key down read only value");
-
-  n1.readOnly = false;
-
-  var n9 = $("n9");
-  is(n9.value, "0", "initial value");
-  n9.select();
-  sendString("4");
-  is(inputEventCount, 1, "input event count");
-  is(inputEventValue, "4", "input value");
-  is(n9.value, "4", "updated value");
-  sendString("2");
-  is(inputEventCount, 2, "input event count");
-  is(inputEventValue, "42", "input value");
-  is(n9.value, "42", "updated value");
-  synthesizeKey("KEY_Backspace");
-  is(inputEventCount, 3, "input event count");
-  is(inputEventValue, "4", "input value");
-  is(n9.value, "4", "updated value");
-  synthesizeKey("A", {accelKey: true});
-  synthesizeKey("KEY_Delete");
-  is(inputEventCount, 4, "input event count");
-  is(inputEventValue, "0", "input value");
-  is(n9.value, "0", "updated value");
-
-  SimpleTest.finish();
-}
-
-var inputEventCount = 0;
-var inputEventValue = null;
-function updateInputEventCount() {
-  inputEventValue = $("n9").value;
-  inputEventCount++;
-};
-
-function testVals(nb, name, valueNumber, valueFieldNumber) {
-  if (valueFieldNumber === undefined)
-    valueFieldNumber = "" + valueNumber;
-
-  SimpleTest.is(nb.value, "" + valueNumber, name + " value is '" + valueNumber + "'");
-  SimpleTest.is(nb.valueNumber, valueNumber, name + " valueNumber is " + valueNumber);
-  SimpleTest.is(nb.inputField.value, valueFieldNumber,
-      name + " inputField value is '" + valueFieldNumber + "'");
-}
-
-function testValsMinMax(nb, name, valueNumber, min, max, valueFieldNumber) {
-  testVals(nb, name, valueNumber, valueFieldNumber);
-  SimpleTest.is(nb.min, min, name + " min is " + min);
-  SimpleTest.is(nb.max, max, name + " max is " + max);
-}
-
-SimpleTest.waitForFocus(doTests);
-
-  ]]></script>
-
-</window>
--- a/toolkit/content/widgets.css
+++ b/toolkit/content/widgets.css
@@ -11,23 +11,20 @@
 @import url("chrome://global/skin/button.css");
 @import url("chrome://global/skin/checkbox.css");
 @import url("chrome://global/skin/dialog.css");
 @import url("chrome://global/skin/dropmarker.css");
 @import url("chrome://global/skin/findBar.css");
 @import url("chrome://global/skin/menu.css");
 @import url("chrome://global/skin/menulist.css");
 @import url("chrome://global/skin/notification.css");
+@import url("chrome://global/skin/numberinput.css");
 @import url("chrome://global/skin/popup.css");
 @import url("chrome://global/skin/radio.css");
 @import url("chrome://global/skin/richlistbox.css");
 @import url("chrome://global/skin/scrollbox.css");
 @import url("chrome://global/skin/splitter.css");
 @import url("chrome://global/skin/tabbox.css");
-
-/* numberbox.css needs to be loaded after textbox.css since it overrides it */
 @import url("chrome://global/skin/textbox.css");
-@import url("chrome://global/skin/numberbox.css");
-
 @import url("chrome://global/skin/toolbar.css");
 @import url("chrome://global/skin/toolbarbutton.css");
 @import url("chrome://global/skin/tree.css");
 @import url("chrome://global/skin/wizard.css");
deleted file mode 100644
--- a/toolkit/content/widgets/numberbox.xml
+++ /dev/null
@@ -1,146 +0,0 @@
-<?xml version="1.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/. -->
-
-
-<bindings id="numberboxBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:html="http://www.w3.org/1999/xhtml"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="numberbox"
-           extends="chrome://global/content/bindings/textbox.xml#textbox">
-    <content>
-      <xul:moz-input-box anonid="moz-input-box" class="numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
-        <html:input class="numberbox-input textbox-input" type="number" anonid="input"
-                    xbl:inherits="value,min,max,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
-      </xul:moz-input-box>
-    </content>
-
-    <implementation>
-      <field name="_valueEntered">false</field>
-      <field name="_value">0</field>
-
-      <property name="value" onget="return String(this.valueNumber)"
-                             onset="return this.valueNumber = val;"/>
-
-      <property name="valueNumber">
-        <getter>
-          if (this._valueEntered) {
-            var newval = this.inputField.value;
-            this._validateValue(newval);
-          }
-          return this._value;
-        </getter>
-        <setter>
-          this._validateValue(val);
-          return val;
-        </setter>
-      </property>
-      <property name="min">
-        <getter>
-          var min = this.getAttribute("min");
-          return min ? Number(min) : 0;
-        </getter>
-        <setter>
-        <![CDATA[
-          if (typeof val == "number") {
-            this.setAttribute("min", val);
-            if (this.valueNumber < val)
-              this._validateValue(val);
-          }
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="max">
-        <getter>
-          var max = this.getAttribute("max");
-          return max ? Number(max) : Infinity;
-        </getter>
-        <setter>
-        <![CDATA[
-          if (typeof val != "number")
-            return val;
-          var min = this.min;
-          if (val < min)
-            val = min;
-          this.setAttribute("max", val);
-          if (this.valueNumber > val)
-            this._validateValue(val);
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <method name="_validateValue">
-        <parameter name="aValue"/>
-        <body>
-          <![CDATA[
-            aValue = Number(aValue) || 0;
-            aValue = Math.round(aValue);
-
-            var min = this.min;
-            var max = this.max;
-            if (aValue < min)
-              aValue = min;
-            else if (aValue > max)
-              aValue = max;
-
-            this._valueEntered = false;
-            this._value = Number(aValue);
-            this.inputField.value = aValue;
-
-            return aValue;
-          ]]>
-        </body>
-      </method>
-
-      <method name="_fireChange">
-        <body>
-          var evt = document.createEvent("Events");
-          evt.initEvent("change", true, true);
-          this.dispatchEvent(evt);
-        </body>
-      </method>
-
-      <constructor><![CDATA[
-        if (this.max < this.min)
-          this.max = this.min;
-
-        var value = this.inputField.value || 0;
-        this._validateValue(value);
-      ]]></constructor>
-
-    </implementation>
-
-    <handlers>
-      <handler event="input" phase="capturing">
-        this._valueEntered = true;
-      </handler>
-
-      <handler event="keypress">
-        <![CDATA[
-          if (!event.ctrlKey && !event.metaKey && !event.altKey && event.charCode) {
-            if (event.charCode == 45 && this.min < 0)
-              return;
-
-            if (event.charCode < 48 || event.charCode > 57)
-              event.preventDefault();
-          }
-        ]]>
-      </handler>
-
-      <handler event="change">
-        if (event.originalTarget == this.inputField) {
-          this._validateValue(this.inputField.value);
-        }
-      </handler>
-    </handlers>
-
-  </binding>
-
-</bindings>
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/textbox.xml
@@ -189,39 +189,37 @@
     </implementation>
 
     <handlers>
       <handler event="focus" phase="capturing">
         <![CDATA[
           if (this.hasAttribute("focused"))
             return;
 
-          let { originalTarget } = event;
-          if (originalTarget == this) {
-            // Forward focus to actual HTML input
-            this.inputField.focus();
-            this.setAttribute("focused", "true");
-            return;
+          switch (event.originalTarget) {
+            case this:
+              // Forward focus to actual HTML input
+              this.inputField.focus();
+              this.setAttribute("focused", "true");
+              break;
+            case this.inputField:
+              if (this.mIgnoreFocus) {
+                this.mIgnoreFocus = false;
+              } else if (this.clickSelectsAll) {
+                try {
+                  if (!this.editor || !this.editor.composing)
+                    this.editor.selectAll();
+                } catch (e) {}
+              }
+              this.setAttribute("focused", "true");
+              break;
+            default:
+              // Otherwise, allow other children (e.g. URL bar buttons) to get focus
+              break;
           }
-
-          // We check for the parent nodes to support input[type=number] where originalTarget may be an
-          // anonymous child input.
-          if (originalTarget == this.inputField ||
-              originalTarget.localName == "input" && originalTarget.parentNode.parentNode == this.inputField) {
-            if (this.mIgnoreFocus) {
-              this.mIgnoreFocus = false;
-            } else if (this.clickSelectsAll) {
-              try {
-                if (!this.editor || !this.editor.composing)
-                  this.editor.selectAll();
-              } catch (e) {}
-            }
-            this.setAttribute("focused", "true");
-          }
-          // Otherwise, allow other children (e.g. URL bar buttons) to get focus
         ]]>
       </handler>
 
       <handler event="blur" phase="capturing">
         <![CDATA[
           this.removeAttribute("focused");
 
           // don't trigger clickSelectsAll when switching application windows
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -557,20 +557,16 @@ html|textarea.textbox-textarea {
 textbox[resizable="true"] > moz-input-box > html|textarea.textbox-textarea {
   resize: both;
 }
 
 textbox[type="search"] {
   -moz-binding: url("chrome://global/content/bindings/textbox.xml#search-textbox");
 }
 
-textbox[type="number"] {
-  -moz-binding: url("chrome://global/content/bindings/numberbox.xml#numberbox");
-}
-
 @supports -moz-bool-pref("layout.css.emulate-moz-box-with-flex") {
   html|*.textbox-input,
   html|*.textbox-textarea {
     /* Be block-level, so that -moz-box-flex can take effect, when we are an item
        in a -moz-box being emulated by modified modern flex. */
     display: block;
   }
 }
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -6,16 +6,17 @@
   == Styles that apply everywhere.
   ======================================================================= */
 
 /* all localizable skin settings shall live here */
 @import url("chrome://global/locale/intl.css");
 @import url("chrome://global/content/widgets.css");
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
 
 %include ../../shared/global.inc.css
 
 /* ::::: XBL bindings ::::: */
 
 @media (-moz-menubar-drag) {
   toolbar[type="menubar"] {
     -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
@@ -116,19 +117,20 @@ toolbar[mode="text"] .toolbarbutton-text
 #print-preview-portrait-button {
   list-style-image: url("moz-icon://stock/gtk-orientation-portrait?size=button");
 }
 
 #print-preview-landscape-button {
   list-style-image: url("moz-icon://stock/gtk-orientation-landscape?size=button");
 }
 
-#print-preview-pageNumber {
-  /* 3 chars + 4px padding left + 2px padding right + 2*6px border */
-  width: calc(18px + 3ch);
+html|*#print-preview-pageNumber {
+  /* 3 chars + (3px border + 1px padding) on both sides */
+  width: calc(8px + 3ch);
+  margin: 0 4px;
 }
 
 /* ::::: miscellaneous formatting ::::: */
 
 :root:-moz-lwtheme {
   -moz-appearance: none;
 }
 
@@ -252,9 +254,8 @@ notification > hbox > button {
   margin-bottom: 0;
 }
 
 popupnotificationcontent {
   margin-top: .5em;
 }
 
 %include ../../shared/notification-popup.inc.css
-
--- a/toolkit/themes/linux/global/in-content/common.css
+++ b/toolkit/themes/linux/global/in-content/common.css
@@ -38,21 +38,16 @@ xul|*.radio-check[selected] {
   background-color: -moz-field;
   fill: -moz-fieldText;
 }
 
 xul|*.radio-label-box {
   -moz-appearance: none;
 }
 
-xul|*.numberbox-input-box {
-  -moz-appearance: none;
-  border-width: 0;
-}
-
 xul|menulist:-moz-focusring > xul|*.menulist-label-box {
   outline: none;
 }
 
 html|input[type="checkbox"]:-moz-focusring + html|label:before {
   outline: 1px dotted;
 }
 
--- a/toolkit/themes/mobile/jar.mn
+++ b/toolkit/themes/mobile/jar.mn
@@ -8,17 +8,17 @@ toolkit.jar:
    skin/classic/global/autocomplete.css                    (global/empty.css)
    skin/classic/global/button.css                          (global/empty.css)
    skin/classic/global/checkbox.css                        (global/empty.css)
    skin/classic/global/dialog.css                          (global/empty.css)
    skin/classic/global/dropmarker.css                      (global/empty.css)
    skin/classic/global/global.css                          (global/empty.css)
    skin/classic/global/menu.css                            (global/empty.css)
    skin/classic/global/menulist.css                        (global/empty.css)
-   skin/classic/global/numberbox.css                       (global/empty.css)
+   skin/classic/global/numberinput.css                     (global/empty.css)
    skin/classic/global/popup.css                           (global/empty.css)
    skin/classic/global/radio.css                           (global/empty.css)
    skin/classic/global/richlistbox.css                     (global/empty.css)
    skin/classic/global/scrollbox.css                       (global/empty.css)
    skin/classic/global/splitter.css                        (global/empty.css)
    skin/classic/global/tabbox.css                          (global/empty.css)
    skin/classic/global/textbox.css                         (global/empty.css)
    skin/classic/global/toolbar.css                         (global/empty.css)
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -65,24 +65,16 @@ xul|tab:-moz-focusring > .tab-middle > .
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
 
 xul|radio[focused="true"] > .radio-check {
   -moz-outline-radius: 100%;
 }
 
-html|*.numberbox-input::-moz-number-spin-up {
-  border-radius: 4px 4px 0 0;
-}
-
-html|*.numberbox-input::-moz-number-spin-down  {
-  border-radius: 0 0 4px 4px;
-}
-
 textbox[type="search"] {
   -moz-appearance: none;
   padding-inline-start: 8px;
   padding-inline-end: 8px;
 }
 
 xul|textbox[type="search"] > moz-input-box > .textbox-search-sign {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
--- a/toolkit/themes/osx/global/notification.css
+++ b/toolkit/themes/osx/global/notification.css
@@ -3,20 +3,15 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %include shared.inc
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 %include ../../shared/notification.inc.css
 %include ../../shared/popupnotification.inc.css
 
-.popup-notification-button:-moz-focusring {
-  outline: 2px -moz-mac-focusring solid;
-  outline-offset: -2px;
-}
-
 .popup-notification-warning {
   color: #aa1b08;
 }
 
 .popup-notification-secondary-button:not([hidden="true"]) ~ .popup-notification-primary-button:not([default]) {
   border-inline-start: 1px solid var(--panel-separator-color);
 }
--- a/toolkit/themes/shared/global.inc.css
+++ b/toolkit/themes/shared/global.inc.css
@@ -91,8 +91,12 @@
 
 .panel-footer > button:not([disabled])[default]:hover {
   background-color: #003eaa;
 }
 
 .panel-footer > button:not([disabled])[default]:hover:active {
   background-color: #002275;
 }
+
+.panel-footer > button > .button-box {
+  padding: 0;
+}
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -160,19 +160,17 @@ html|button {
   padding: 3px;
 }
 
 /* xul buttons and menulists */
 
 *|button,
 html|select,
 html|input[type="color"],
-xul|menulist,
-html|*.numberbox-input::-moz-number-spin-up,
-html|*.numberbox-input::-moz-number-spin-down {
+xul|menulist {
   -moz-appearance: none;
   min-height: 32px;
   /* !important overrides button.css for disabled and default XUL buttons: */
   color: inherit !important;
   border: none;
   border-radius: 2px;
   background-color: var(--in-content-button-background);
   font-weight: 400;
@@ -194,19 +192,17 @@ html|button::-moz-focus-inner,
 html|select::-moz-focus-inner,
 html|input[type="color"]::-moz-focus-inner {
   border: none;
 }
 
 *|button:-moz-focusring,
 html|select:-moz-focusring,
 html|input[type="color"]:-moz-focusring,
-xul|menulist:-moz-focusring,
-html|input[type="number"]:-moz-focusring::-moz-number-spin-up,
-html|input[type="number"]:-moz-focusring::-moz-number-spin-down {
+xul|menulist:-moz-focusring {
   outline: none;
   box-shadow: 0 0 0 1px var(--in-content-border-active) inset,
     0 0 0 1px var(--in-content-border-active),
     0 0 0 4px var(--in-content-border-active-shadow);
 }
 
 html|select:not([size]):not([multiple]) {
   background-image: url("chrome://global/skin/in-content/dropdown.svg#dropdown");
@@ -220,37 +216,32 @@ html|select:not([size]):not([multiple]) 
 }
 
 html|select:not([size]):not([multiple]):dir(rtl) {
   background-position: left 3px center;
 }
 
 html|button:enabled:hover,
 html|select:not([size]):not([multiple]):enabled:hover,
-html|*.numberbox-input::-moz-number-spin-up:hover,
-html|*.numberbox-input::-moz-number-spin-down:hover,
 html|input[type="color"]:hover,
 xul|button:not([disabled="true"]):hover,
 xul|menulist:not([disabled="true"]):hover {
   background-color: var(--in-content-button-background-hover);
 }
 
 html|button:enabled:hover:active,
 html|select:not([size]):not([multiple]):enabled:hover:active,
-html|*.numberbox-input::-moz-number-spin-up:hover:active,
-html|*.numberbox-input::-moz-number-spin-down:hover:active,
 html|input[type="color"]:enabled:hover:active,
 xul|button:not([disabled="true"]):hover:active,
 xul|menulist[open="true"]:not([disabled="true"]) {
   background-color: var(--in-content-button-background-active);
 }
 
 html|button:disabled,
 html|select:disabled,
-html|*.numberbox-input:disabled::-moz-number-spin-box,
 html|input[type="color"]:disabled,
 xul|button[disabled="true"],
 xul|menulist[disabled="true"] {
   opacity: 0.4;
 }
 
 *|button.primary {
   background-color: var(--in-content-primary-button-background);
@@ -292,38 +283,16 @@ xul|button[type="menu"] > xul|*.button-b
 
 xul|*.close-icon > xul|*.button-box {
   padding-top: 0;
   padding-bottom: 0;
   padding-right: 0 !important;
   padding-left: 0 !important;
 }
 
-html|*.numberbox-input::-moz-number-spin-box {
-  margin-inline-end: 1px;
-}
-
-html|*.numberbox-input::-moz-number-spin-up,
-html|*.numberbox-input::-moz-number-spin-down {
-  padding: 5px 8px;
-  margin: 0;
-  min-height: initial;
-  background-position: center;
-}
-
-html|*.numberbox-input::-moz-number-spin-up {
-  border-radius: 1px 1px 0 0;
-  background-image: url("chrome://global/skin/arrow/arrow-up.gif");
-}
-
-html|*.numberbox-input::-moz-number-spin-down {
-  border-radius: 0 0 1px 1px;
-  background-image: url("chrome://global/skin/arrow/arrow-dn.gif");
-}
-
 xul|*.menulist-dropmarker {
   -moz-appearance: none;
   margin-inline-end: 4px;
   padding: 0;
   border: none;
   background-color: transparent;
   list-style-image: url("chrome://global/skin/in-content/dropdown.svg");
   -moz-context-properties: fill;
@@ -413,24 +382,16 @@ xul|textbox {
 }
 
 xul|textbox {
   min-height: 30px;
   padding-right: 8px;
   padding-left: 8px;
 }
 
-xul|textbox[type="number"] {
-  padding-inline-end: 0;
-}
-
-html|*.numberbox-input::-moz-number-text {
-  margin-inline-end: 10px;
-}
-
 /* Create a separate rule to unset these styles on .tree-input instead of
    using :not(.tree-input) so the selector specifity doesn't change. */
 xul|textbox.tree-input {
   min-height: unset;
   padding-right: unset;
   padding-left: unset;
 }
 
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -17,17 +17,17 @@ toolkit.jar:
   skin/classic/global/aboutMemory.css                      (../../shared/aboutMemory.css)
 * skin/classic/global/aboutReader.css                      (../../shared/aboutReader.css)
   skin/classic/global/aboutRights.css                      (../../shared/aboutRights.css)
   skin/classic/global/aboutLicense.css                     (../../shared/aboutLicense.css)
   skin/classic/global/aboutSupport.css                     (../../shared/aboutSupport.css)
   skin/classic/global/appPicker.css                        (../../shared/appPicker.css)
   skin/classic/global/config.css                           (../../shared/config.css)
   skin/classic/global/datetimeinputpickers.css             (../../shared/datetimeinputpickers.css)
-  skin/classic/global/numberbox.css                        (../../shared/numberbox.css)
+  skin/classic/global/numberinput.css                      (../../shared/numberinput.css)
   skin/classic/global/passwordmgr.css                      (../../shared/passwordmgr.css)
   skin/classic/global/icons/autoscroll.svg                 (../../shared/icons/autoscroll.svg)
   skin/classic/global/icons/autoscroll-horizontal.svg      (../../shared/icons/autoscroll-horizontal.svg)
   skin/classic/global/icons/autoscroll-vertical.svg        (../../shared/icons/autoscroll-vertical.svg)
   skin/classic/global/icons/calendar-arrow-left.svg        (../../shared/icons/calendar-arrow-left.svg)
   skin/classic/global/icons/calendar-arrow-right.svg       (../../shared/icons/calendar-arrow-right.svg)
   skin/classic/global/icons/check.svg                      (../../shared/icons/check.svg)
   skin/classic/global/icons/check-partial.svg              (../../shared/icons/check-partial.svg)
--- a/toolkit/themes/shared/non-mac.jar.inc.mn
+++ b/toolkit/themes/shared/non-mac.jar.inc.mn
@@ -10,18 +10,16 @@
 
 #include jar.inc.mn
 
   skin/classic/global/dialog.css                           (../../windows/global/dialog.css)
   skin/classic/global/scrollbars.css                       (../../windows/global/xulscrollbars.css)
   skin/classic/global/tabprompts.css                       (../../windows/global/tabprompts.css)
   skin/classic/global/wizard.css                           (../../windows/global/wizard.css)
 
-  skin/classic/global/arrow/arrow-dn.gif                   (../../windows/global/arrow/arrow-dn.gif)
-  skin/classic/global/arrow/arrow-up.gif                   (../../windows/global/arrow/arrow-up.gif)
   skin/classic/global/arrow/panelarrow-horizontal.svg      (../../windows/global/arrow/panelarrow-horizontal.svg)
   skin/classic/global/arrow/panelarrow-vertical.svg        (../../windows/global/arrow/panelarrow-vertical.svg)
 
 * skin/classic/global/dirListing/dirListing.css            (../../windows/global/dirListing/dirListing.css)
   skin/classic/global/icons/error-16.png                   (../../windows/global/icons/error-16.png)
   skin/classic/global/icons/question-16.png                (../../windows/global/icons/question-16.png)
   skin/classic/global/icons/question-64.png                (../../windows/global/icons/question-64.png)
   skin/classic/global/icons/search-textbox.svg             (../../windows/global/icons/search-textbox.svg)
rename from toolkit/themes/shared/numberbox.css
rename to toolkit/themes/shared/numberinput.css
--- a/toolkit/themes/shared/numberbox.css
+++ b/toolkit/themes/shared/numberinput.css
@@ -1,42 +1,35 @@
 /* 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/. */
 
-/* ===== numberbox.css ==================================================
-  == Styles used by the XUL textbox type="number" element.
+/* ===== numberinput.css ================================================
+  == Styles used by the input[type="number"] element.
   ======================================================================= */
 
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
-html|*.numberbox-input {
+html|input[type="number"] {
   text-align: right;
 }
 
-textbox[type="number"][hidespinbuttons="true"] html|*.numberbox-input {
-  -moz-appearance: textfield;
+html|input[type="number"][hidespinbuttons="true"] {
+  -moz-appearance: textfield !important;
 }
 
 /* input[type=number] uses display: flex; by default which is incompatible with XUL flexbox
    Forcing XUL flexbox allows changing the size of the input. */
-html|*.numberbox-input,
-html|*.numberbox-input::-moz-number-wrapper,
-html|*.numberbox-input::-moz-number-spin-box {
+xul|*:root html|input[type="number"],
+xul|*:root html|input[type="number"]::-moz-number-wrapper {
   display: -moz-box;
   -moz-box-align: center;
 }
 
-html|*.numberbox-input::-moz-number-spin-box {
-  -moz-box-orient: vertical;
-}
-
-html|*.numberbox-input::-moz-number-wrapper,
-html|*.numberbox-input::-moz-number-spin-up,
-html|*.numberbox-input::-moz-number-spin-down,
-html|*.numberbox-input::-moz-number-text {
+xul|*:root html|input[type="number"]::-moz-number-wrapper,
+xul|*:root html|input[type="number"]::-moz-number-text {
   -moz-box-flex: 1;
 }
 
-html|*.numberbox-input::-moz-number-wrapper {
+xul|*:root html|input[type="number"]::-moz-number-wrapper {
   width: 100%;
 }
--- a/toolkit/themes/shared/popupnotification.inc.css
+++ b/toolkit/themes/shared/popupnotification.inc.css
@@ -63,20 +63,16 @@
 .popup-notification-primary-button:not([alone]) {
   flex: 0 50%;
 }
 
 .popup-notification-secondary-button[hidden="true"] ~ .popup-notification-primary-button {
   flex: 1;
 }
 
-.popup-notification-button > .button-box {
-  padding: 0;
-}
-
 .popup-notification-dropmarker {
   flex: none;
   padding: 0 15px;
 }
 
 .popup-notification-dropmarker > .button-box > hbox {
   display: none;
 }
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -6,16 +6,17 @@
   == Styles that apply everywhere.
   ======================================================================= */
 
 /* all localizable skin settings shall live here */
 @import url("chrome://global/locale/intl.css");
 @import url("chrome://global/content/widgets.css");
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
 
 %include ../../shared/global.inc.css
 
 /* ::::: Variables ::::: */
 :root {
   --arrowpanel-padding: 10px;
   --default-arrowpanel-background: -moz-field;
   --default-arrowpanel-color: -moz-fieldText;
@@ -121,18 +122,20 @@ toolbar[mode="text"] .toolbarbutton-text
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 #print-preview-landscape-button {
   list-style-image: url("chrome://global/skin/icons/Print-preview.png");
   -moz-image-region: rect(0px 32px 16px 16px);
 }
 
-#print-preview-pageNumber {
-  width: 3ch;
+html|*#print-preview-pageNumber {
+  /* 3 chars + (3px border + 1px padding) on both sides */
+  width: calc(8px + 3ch);
+  margin: 0 4px;
 }
 
 /* ::::: miscellaneous formatting ::::: */
 
 :root[lwtheme-image]:-moz-lwtheme-darktext {
   text-shadow: 0 -0.5px 1.5px white;
 }
 
@@ -255,9 +258,8 @@ label[disabled="true"] {
   outline: 1px dotted;
 }
 
 popupnotificationcontent {
   margin-top: .5em;
 }
 
 %include ../../shared/notification-popup.inc.css
-
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -28,17 +28,19 @@ toolkit.jar:
   skin/classic/global/toolbarbutton.css
   skin/classic/global/tooltip.css
 * skin/classic/global/tree.css
 * skin/classic/global/alerts/alert.css                     (alerts/alert.css)
   skin/classic/global/arrow/arrow-lft.gif                  (arrow/arrow-lft.gif)
   skin/classic/global/arrow/arrow-lft-dis.gif              (arrow/arrow-lft-dis.gif)
   skin/classic/global/arrow/arrow-rit.gif                  (arrow/arrow-rit.gif)
   skin/classic/global/arrow/arrow-rit-dis.gif              (arrow/arrow-rit-dis.gif)
+  skin/classic/global/arrow/arrow-up.gif                   (arrow/arrow-up.gif)
   skin/classic/global/arrow/arrow-up-dis.gif               (arrow/arrow-up-dis.gif)
+  skin/classic/global/arrow/arrow-dn.gif                   (arrow/arrow-dn.gif)
   skin/classic/global/arrow/arrow-dn-dis.gif               (arrow/arrow-dn-dis.gif)
   skin/classic/global/dirListing/folder.png                (dirListing/folder.png)
   skin/classic/global/dirListing/up.png                    (dirListing/up.png)
   skin/classic/global/icons/blocklist_favicon.png          (icons/blocklist_favicon.png)
   skin/classic/global/icons/Error.png                      (icons/Error.png)
   skin/classic/global/icons/collapse.png                   (icons/collapse.png)
   skin/classic/global/icons/expand.png                     (icons/expand.png)
   skin/classic/global/icons/Landscape.png                  (icons/Landscape.png)