Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 16 Jan 2017 13:18:17 -0800
changeset 357620 88030580b14bb253a55bc174c987a9fa43c3fb55
parent 357602 98cbc58cc6bc7bbdd6af853173b1c12b10ad47a1 (current diff)
parent 357619 99ea5dbd29164d1d0a9bc3397b9ae5e1e57290be (diff)
child 357628 7ccd359d1e5e1f64c668cd6ef41a17016fda74ec
child 357656 16e87b06a1d0673cef4845e3de711cbb3d483842
push id10621
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 16:02:43 +0000
treeherdermozilla-aurora@dca7b42e6c67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone53.0a1
Merge m-i to m-c, a=merge
browser/modules/test/browser_UsageTelemetry.js
browser/modules/webrtcUI.jsm
dom/ipc/TabChild.cpp
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -471,28 +471,32 @@ social.error.message=%1$S is unable to c
 social.error.tryAgain.label=Try Again
 social.error.tryAgain.accesskey=T
 social.error.closeSidebar.label=Close This Sidebar
 social.error.closeSidebar.accesskey=C
 
 # LOCALIZATION NOTE: %1$S is the label for the toolbar button, %2$S is the associated badge numbering that the social provider may provide.
 social.aria.toolbarButtonBadgeText=%1$S (%2$S)
 
-# LOCALIZATION NOTE (getUserMedia.shareCamera2.message, getUserMedia.shareMicrophone2.message,
-#                    getUserMedia.shareScreen2.message, getUserMedia.shareCameraAndMicrophone2.message,
-#                    getUserMedia.shareScreenAndMicrophone2.message, getUserMedia.shareCameraAndAudioCapture2.message,
-#                    getUserMedia.shareAudioCapture2.message, getUserMedia.shareScreenAndAudioCapture2.message):
-#  %S is the website origin (e.g. www.mozilla.org)
+# LOCALIZATION NOTE (getUserMedia.shareCamera2.message,
+#                    getUserMedia.shareMicrophone2.message,
+#                    getUserMedia.shareScreen3.message,
+#                    getUserMedia.shareCameraAndMicrophone2.message,
+#                    getUserMedia.shareCameraAndAudioCapture2.message,
+#                    getUserMedia.shareScreenAndMicrophone3.message,
+#                    getUserMedia.shareScreenAndAudioCapture3.message,
+#                    getUserMedia.shareAudioCapture2.message):
+# %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.shareCamera2.message = Will you allow %S to use your camera?
 getUserMedia.shareMicrophone2.message = Will you allow %S to use your microphone?
-getUserMedia.shareScreen2.message = Will you allow %S to see your screen or application window?
+getUserMedia.shareScreen3.message = Will you allow %S to see your screen?
 getUserMedia.shareCameraAndMicrophone2.message = Will you allow %S to use your camera and microphone?
 getUserMedia.shareCameraAndAudioCapture2.message = Will you allow %S to use your camera and listen to this tab’s audio?
-getUserMedia.shareScreenAndMicrophone2.message = Will you allow %S to use your microphone and see your screen or application window?
-getUserMedia.shareScreenAndAudioCapture2.message = Will you allow %S to listen to this tab’s audio and see your screen or application window?
+getUserMedia.shareScreenAndMicrophone3.message = Will you allow %S to use your microphone and see your screen?
+getUserMedia.shareScreenAndAudioCapture3.message = Will you allow %S to listen to this tab’s audio and see your screen?
 getUserMedia.shareAudioCapture2.message = Will you allow %S to listen to this tab’s audio?
 # LOCALIZATION NOTE (getUserMedia.shareScreenWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %S will be the 'learn more' link
 getUserMedia.shareScreenWarning.message = Only share screens with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %S
 # LOCALIZATION NOTE (getUserMedia.shareFirefoxWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %1$S is brandShortName (eg. Firefox)
 # %2$S will be the 'learn more' link
 getUserMedia.shareFirefoxWarning.message = Only share %1$S with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %2$S
@@ -513,26 +517,30 @@ getUserMedia.shareEntireScreen.label = E
 # Example: Screen 1, Screen 2,..
 getUserMedia.shareMonitor.label = Screen %S
 # LOCALIZATION NOTE (getUserMedia.shareApplicationWindowCount.label):
 # Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Replacement for #1 is the name of the application.
 # Replacement for #2 is the number of windows currently displayed by the application.
 getUserMedia.shareApplicationWindowCount.label=#1 (#2 window);#1 (#2 windows)
+# LOCALIZATION NOTE (getUserMedia.allow.label,
+#                    getUserMedia.dontAllow.label):
+# These two buttons are the possible answers to the various prompts in the
+# "getUserMedia.share{device}.message" strings.
 getUserMedia.allow.label = Allow
 getUserMedia.allow.accesskey = A
 getUserMedia.dontAllow.label = Don’t Allow
 getUserMedia.dontAllow.accesskey = D
 getUserMedia.remember=Remember this decision
-# LOCALIZATION NOTE (ggetUserMedia.reasonForNoPermanentAllow.screen,
+# LOCALIZATION NOTE (getUserMedia.reasonForNoPermanentAllow.screen2,
 #                    getUserMedia.reasonForNoPermanentAllow.audio,
 #                    getUserMedia.reasonForNoPermanentAllow.insecure):
 # %S is brandShortName
-getUserMedia.reasonForNoPermanentAllow.screen=%S can not allow permanent access to your screen or application without asking which one to share.
+getUserMedia.reasonForNoPermanentAllow.screen2=%S can not allow permanent access to your screen without asking which one to share.
 getUserMedia.reasonForNoPermanentAllow.audio=%S can not allow permanent access to your tab’s audio without asking which tab to share.
 getUserMedia.reasonForNoPermanentAllow.insecure=Your connection to this site is not secure. To protect you, %S will only allow access for this session.
 
 getUserMedia.sharingMenu.label = Tabs sharing devices
 getUserMedia.sharingMenu.accesskey = d
 # LOCALIZATION NOTE (getUserMedia.sharingMenuCamera
 #                    getUserMedia.sharingMenuMicrophone,
 #                    getUserMedia.sharingMenuAudioCapture,
--- a/browser/modules/test/browser_UsageTelemetry.js
+++ b/browser/modules/test/browser_UsageTelemetry.js
@@ -59,18 +59,17 @@ let checkScalar = (scalars, scalarName, 
   }
   ok(!(scalarName in scalars), scalarName + " must not be reported.");
 };
 
 /**
  * Get a snapshot of the scalars and check them against the provided values.
  */
 let checkScalars = (countsObject) => {
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = 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.");
@@ -182,18 +181,17 @@ add_task(function* test_subsessionSplit(
 });
 
 add_task(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 =
-      Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    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.");
   };
 
--- a/browser/modules/test/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser_UsageTelemetry_content.js
@@ -66,18 +66,17 @@ add_task(function* test_context_menu() {
                                            gBrowser.selectedBrowser);
   yield popupPromise;
 
   info("Click on search.");
   let searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
   searchItem.click();
 
   info("Validate the search metrics.");
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -103,18 +102,17 @@ add_task(function* test_about_newtab() {
 
   info("Trigger a simple serch, just text + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
   yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
--- a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
@@ -66,18 +66,17 @@ add_task(function* test_abouthome_simple
 
   info("Trigger a simple serch, just test + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield typeInSearchField(tab.linkedBrowser, "test query", "searchText");
   yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
--- a/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
+++ b/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
@@ -22,18 +22,17 @@ add_task(function* test_privateMode() {
   Services.telemetry.clearScalars();
 
   // Open a private window and load a website in it.
   let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
   yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
 
   // Check that tab and window count is recorded.
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = 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.");
@@ -71,18 +70,17 @@ add_task(function* test_sessionRestore()
 
   // 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));
   yield tabRestored;
 
   // Check that the URI is not recorded.
-  const scalars =
-    Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = 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_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_searchbar.js
@@ -92,18 +92,17 @@ add_task(function* test_plainQuery() {
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("simple query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -127,18 +126,17 @@ add_task(function* test_oneOff() {
   yield searchInSearchbar("query");
 
   info("Pressing Alt+Down to highlight the first one off engine.");
   EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -173,18 +171,17 @@ add_task(function* test_suggestion() {
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("query");
   info("Clicking the searchbar suggestion.");
   clickSearchbarSuggestion("queryfoo");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
--- a/browser/modules/test/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_urlbar.js
@@ -111,18 +111,17 @@ add_task(function* test_simpleQuery() {
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("simple query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -157,18 +156,17 @@ add_task(function* test_searchAlias() {
 
   info("Search using a search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("mozalias query");
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -206,18 +204,17 @@ add_task(function* test_oneOff() {
   yield searchInAwesomebar("query");
 
   info("Pressing Alt+Down to take us to the first one-off engine.");
   EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
   EventUtils.sendKey("return");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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);
 
   // Also check events.
@@ -266,18 +263,17 @@ add_task(function* test_suggestion() {
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("query");
   info("Clicking the urlbar suggestion.");
   clickURLBarSuggestion("queryfoo");
   yield p;
 
   // Check if the scalars contain the expected values.
-  const scalars =
-    Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
   checkKeyedScalar(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-' + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + '.urlbar', 1);
 
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -90,16 +90,26 @@ function getSearchCountsHistogram() {
  * Check that the keyed histogram contains the right value.
  */
 function checkKeyedHistogram(h, key, expectedValue) {
   const snapshot = h.snapshot();
   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 scalars = aKeyed ?
+    Services.telemetry.snapshotKeyedScalars(aChannel, aClear)["default"] :
+    Services.telemetry.snapshotScalars(aChannel, aClear)["default"];
+  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.");
 
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -330,35 +330,51 @@ function getHost(uri, href) {
       let bundle = Services.strings.createBundle(kBundleURI);
       host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
     }
   }
   return host;
 }
 
 function prompt(aBrowser, aRequest) {
-  let {audioDevices: audioDevices, videoDevices: videoDevices,
-       sharingScreen: sharingScreen, sharingAudio: sharingAudio,
-       requestTypes: requestTypes} = aRequest;
+  let { audioDevices, videoDevices, sharingScreen, sharingAudio,
+        requestTypes } = aRequest;
 
   // If the user has already denied access once in this tab,
   // deny again without even showing the notification icon.
   if ((audioDevices.length && SitePermissions
         .get(null, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
       (videoDevices.length && SitePermissions
         .get(null, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
     denyRequest(aBrowser, aRequest);
     return;
   }
 
   let uri = Services.io.newURI(aRequest.documentURI);
   let host = getHost(uri);
   let chromeDoc = aBrowser.ownerDocument;
   let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
-  let stringId = "getUserMedia.share" + requestTypes.join("And") + "2.message";
+
+  // Mind the order, because for simplicity we're iterating over the list using
+  // "includes()". This allows the rotation of string identifiers. We list the
+  // full identifiers here so they can be cross-referenced more easily.
+  let joinedRequestTypes = requestTypes.join("And");
+  let stringId = [
+    // Individual request types first.
+    "getUserMedia.shareCamera2.message",
+    "getUserMedia.shareMicrophone2.message",
+    "getUserMedia.shareScreen3.message",
+    "getUserMedia.shareAudioCapture2.message",
+    // Combinations of the above request types last.
+    "getUserMedia.shareCameraAndMicrophone2.message",
+    "getUserMedia.shareCameraAndAudioCapture2.message",
+    "getUserMedia.shareScreenAndMicrophone3.message",
+    "getUserMedia.shareScreenAndAudioCapture3.message",
+  ].find(id => id.includes(joinedRequestTypes));
+
   let message = stringBundle.getFormattedString(stringId, [host]);
 
   let notification; // Used by action callbacks.
   let mainAction = {
     label: stringBundle.getString("getUserMedia.allow.label"),
     accessKey: stringBundle.getString("getUserMedia.allow.accesskey"),
     // The real callback will be set during the "showing" event. The
     // empty function here is so that PopupNotifications.show doesn't
@@ -388,17 +404,17 @@ function prompt(aBrowser, aRequest) {
 
   let productName = gBrandBundle.GetStringFromName("brandShortName");
 
   // Disable the permanent 'Allow' action if the connection isn't secure, or for
   // screen/audio sharing (because we can't guess which window the user wants to
   // share without prompting).
   let reasonForNoPermanentAllow = "";
   if (sharingScreen) {
-    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen";
+    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen2";
   } else if (sharingAudio) {
     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.audio";
   } else if (!aRequest.secure) {
     reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.insecure";
   }
 
   let options = {
     persistent: true,
--- a/dom/file/FileReader.cpp
+++ b/dom/file/FileReader.cpp
@@ -413,16 +413,17 @@ FileReader::ReadFileContent(Blob& aBlob,
     return;
   }
 
   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
     mFileData = js_pod_malloc<char>(mTotal);
     if (!mFileData) {
       NS_WARNING("Preallocation failed for ReadFileData");
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
     }
   }
 
   aRv = DoAsyncWait();
   if (NS_WARN_IF(aRv.Failed())) {
     FreeFileData();
     return;
   }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4760,16 +4760,32 @@ ContentParent::RecvAccumulateChildHistog
 mozilla::ipc::IPCResult
 ContentParent::RecvAccumulateChildKeyedHistogram(
                 InfallibleTArray<KeyedAccumulation>&& aAccumulations)
 {
   Telemetry::AccumulateChildKeyed(GeckoProcessType_Content, aAccumulations);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvUpdateChildScalars(
+                InfallibleTArray<ScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildScalars(GeckoProcessType_Content, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvUpdateChildKeyedScalars(
+                InfallibleTArray<KeyedScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildKeyedScalars(GeckoProcessType_Content, aScalarActions);
+  return IPC_OK();
+}
+
 PURLClassifierParent*
 ContentParent::AllocPURLClassifierParent(const Principal& aPrincipal,
                                          const bool& aUseTrackingProtection,
                                          bool* aSuccess)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   *aSuccess = true;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1077,16 +1077,20 @@ private:
                                                       const bool& aRecursiveFlag) override;
 
   virtual mozilla::ipc::IPCResult RecvDeleteGetFilesRequest(const nsID& aID) override;
 
   virtual mozilla::ipc::IPCResult RecvAccumulateChildHistogram(
     InfallibleTArray<Accumulation>&& aAccumulations) override;
   virtual mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram(
     InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateChildScalars(
+    InfallibleTArray<ScalarAction>&& aScalarActions) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
+    InfallibleTArray<KeyedScalarAction>&& aScalarActions) override;
 public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
 private:
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -90,16 +90,18 @@ using struct LookAndFeelInt from "mozill
 using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
 using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h";
 using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1145,16 +1147,18 @@ parent:
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
     /**
      * Messages for communicating child Telemetry to the parent process
      */
     async AccumulateChildHistogram(Accumulation[] accumulations);
     async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+    async UpdateChildScalars(ScalarAction[] updates);
+    async UpdateChildKeyedScalars(KeyedScalarAction[] updates);
 
     sync GetA11yContentId() returns (uint32_t aContentId);
 
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -376,16 +376,17 @@ TabChild::TabChild(nsIContentChild* aMan
   , mRounding(0)
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(false)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mIsFreshProcess(false)
+  , mSkipKeyPress(false)
   , mLayerObserverEpoch(0)
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   , mNativeWindowHandle(0)
 #endif
 {
   nsWeakPtr weakPtrThis(do_GetWeakReference(static_cast<nsITabChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback = [weakPtrThis](uint64_t aInputBlockId,
                                                    const nsTArray<TouchBehaviorFlags>& aFlags)
@@ -1771,20 +1772,62 @@ TabChild::RequestNativeKeyBindings(AutoC
 mozilla::ipc::IPCResult
 TabChild::RecvNativeSynthesisResponse(const uint64_t& aObserverId,
                                       const nsCString& aResponse)
 {
   mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId, aResponse.get());
   return IPC_OK();
 }
 
+// In case handling repeated keys takes much time, we skip firing new ones.
+bool
+TabChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent)
+{
+  if (mRepeatedKeyEventTime.IsNull() ||
+      !aEvent.mIsRepeat ||
+      (aEvent.mMessage != eKeyDown && aEvent.mMessage != eKeyPress)) {
+    mRepeatedKeyEventTime = TimeStamp();
+    mSkipKeyPress = false;
+    return false;
+  }
+
+  if ((aEvent.mMessage == eKeyDown &&
+       (mRepeatedKeyEventTime > aEvent.mTimeStamp)) ||
+      (mSkipKeyPress && (aEvent.mMessage == eKeyPress))) {
+    // If we skip a keydown event, also the following keypress events should be
+    // skipped.
+    mSkipKeyPress |= aEvent.mMessage == eKeyDown;
+    return true;
+  }
+
+  if (aEvent.mMessage == eKeyDown) {
+    // If keydown wasn't skipped, nor should the possible following keypress.
+    mRepeatedKeyEventTime = TimeStamp();
+    mSkipKeyPress = false;
+  }
+  return false;
+}
+
+void
+TabChild::UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent)
+{
+  if (aEvent.mIsRepeat &&
+      (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress)) {
+    mRepeatedKeyEventTime = TimeStamp::Now();
+  }
+}
+
 mozilla::ipc::IPCResult
 TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& event,
                            const MaybeNativeKeyBinding& aBindings)
 {
+  if (SkipRepeatedKeyEvent(event)) {
+    return IPC_OK();
+  }
+
   AutoCacheNativeKeyCommands autoCache(mPuppetWidget);
 
   if (event.mMessage == eKeyPress) {
     // If content code called preventDefault() on a keydown event, then we don't
     // want to process any following keypress events.
     if (mIgnoreKeyPressEvent) {
       return IPC_OK();
     }
@@ -1797,16 +1840,20 @@ TabChild::RecvRealKeyEvent(const WidgetK
       autoCache.CacheNoCommands();
     }
   }
 
   WidgetKeyboardEvent localEvent(event);
   localEvent.mWidget = mPuppetWidget;
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
+  // Update the end time of the possible repeated event so that we can skip
+  // some incoming events in case event handling took long time.
+  UpdateRepeatedKeyEventEndTime(localEvent);
+
   if (event.mMessage == eKeyDown) {
     mIgnoreKeyPressEvent = status == nsEventStatus_eConsumeNoDefault;
   }
 
   if (localEvent.mFlags.mIsSuppressedOrDelayed) {
     localEvent.PreventDefault();
   }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -742,16 +742,20 @@ private:
 
   ScreenIntRect GetOuterRect();
 
   void SetUnscaledInnerSize(const CSSSize& aSize)
   {
     mUnscaledInnerSize = aSize;
   }
 
+  bool SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent);
+
+  void UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent);
+
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
   RenderFrameChild* mRemoteFrame;
   RefPtr<nsIContentChild> mManager;
@@ -790,16 +794,23 @@ private:
 
   bool mIPCOpen;
   bool mParentIsActive;
   CSSSize mUnscaledInnerSize;
   bool mDidSetRealShowInfo;
   bool mDidLoadURLInit;
   bool mIsFreshProcess;
 
+  bool mSkipKeyPress;
+
+  // Store the end time of the handling of the last repeated keydown/keypress
+  // event so that in case event handling takes time, some repeated events can
+  // be skipped to not flood child process.
+  mozilla::TimeStamp mRepeatedKeyEventTime;
+
   AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
 
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   uint64_t mLayerObserverEpoch;
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -16,16 +16,18 @@ support-files =
 disabled = Does not reliably pass on 32-bit systems - bug 1314098
 skip-if = !e10s
 [browser_autofocus_background.js]
 [browser_autofocus_preference.js]
 [browser_bug396843.js]
 [browser_bug1004814.js]
 [browser_bug1008941_dismissGeolocationHanger.js]
 [browser_bug1238427.js]
+[browser_bug1316330.js]
+skip-if = !e10s
 [browser_cancel_keydown_keypress_event.js]
 support-files =
   prevent_return_key.html
 [browser_ConsoleAPI_originAttributes.js]
 [browser_ConsoleAPITests.js]
 skip-if = e10s
 [browser_ConsoleStorageAPITests.js]
 [browser_ConsoleStoragePBTest_perwindowpb.js]
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_bug1316330.js
@@ -0,0 +1,54 @@
+const URL =
+  "data:text/html,<script>" +
+  "window.focus();" + 
+  "var down = 0; var press = 0;" +
+  "onkeydown = function(e) {" +
+  "  document.body.setAttribute('data-down', ++down);" +
+  "  if (e.keyCode == 'D') while (Date.now() - startTime < 500) {}" +
+  "};" +
+  "onkeypress = function(e) {" +
+  "  document.body.setAttribute('data-press', ++press);" +
+  "  if (e.charCode == 'P') while (Date.now() - startTime < 500) {}" +
+  "};" +
+  "</script>";
+
+function createKeyEvent(type, id, repeated) {
+  var code = id.charCodeAt(0);
+  return new KeyboardEvent(type, { keyCode: code, charCode: code, bubbles: true, repeat: repeated });
+}
+
+add_task(function* () {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+  let browser = tab.linkedBrowser;
+
+  // Need to dispatch a DOM Event explicitly via PresShell to get KeyEvent with .repeat = true to 
+  // be handled by EventStateManager, which then forwards the event to the child process.
+  var utils = EventUtils._getDOMWindowUtils(window);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "D", true), true);
+
+  yield ContentTask.spawn(browser, null, function* () {
+    is(content.document.body.getAttribute("data-down"), "2", "Correct number of events");
+    is(content.document.body.getAttribute("data-press"), "2", "Correct number of events");
+  });
+
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "P", true), true);
+
+  yield ContentTask.spawn(browser, null, function* () {
+    is(content.document.body.getAttribute("data-down"), "4", "Correct number of events");
+    is(content.document.body.getAttribute("data-press"), "4", "Correct number of events");
+  });
+
+  gBrowser.removeCurrentTab();
+});
--- a/dom/workers/ServiceWorkerJob.cpp
+++ b/dom/workers/ServiceWorkerJob.cpp
@@ -48,21 +48,21 @@ ServiceWorkerJob::IsEquivalentTo(Service
          mScriptSpec.Equals(aJob->mScriptSpec) &&
          mPrincipal->Equals(aJob->mPrincipal);
 }
 
 void
 ServiceWorkerJob::AppendResultCallback(Callback* aCallback)
 {
   AssertIsOnMainThread();
-  MOZ_ASSERT(mState != State::Finished);
-  MOZ_ASSERT(aCallback);
-  MOZ_ASSERT(mFinalCallback != aCallback);
+  MOZ_DIAGNOSTIC_ASSERT(mState != State::Finished);
+  MOZ_DIAGNOSTIC_ASSERT(aCallback);
+  MOZ_DIAGNOSTIC_ASSERT(mFinalCallback != aCallback);
   MOZ_ASSERT(!mResultCallbackList.Contains(aCallback));
-  MOZ_ASSERT(!mResultCallbacksInvoked);
+  MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked);
   mResultCallbackList.AppendElement(aCallback);
 }
 
 void
 ServiceWorkerJob::StealResultCallbacksFrom(ServiceWorkerJob* aJob)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aJob);
@@ -79,24 +79,24 @@ ServiceWorkerJob::StealResultCallbacksFr
     AppendResultCallback(callback);
   }
 }
 
 void
 ServiceWorkerJob::Start(Callback* aFinalCallback)
 {
   AssertIsOnMainThread();
-  MOZ_ASSERT(!mCanceled);
+  MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
 
-  MOZ_ASSERT(aFinalCallback);
-  MOZ_ASSERT(!mFinalCallback);
+  MOZ_DIAGNOSTIC_ASSERT(aFinalCallback);
+  MOZ_DIAGNOSTIC_ASSERT(!mFinalCallback);
   MOZ_ASSERT(!mResultCallbackList.Contains(aFinalCallback));
   mFinalCallback = aFinalCallback;
 
-  MOZ_ASSERT(mState == State::Initial);
+  MOZ_DIAGNOSTIC_ASSERT(mState == State::Initial);
   mState = State::Started;
 
   nsCOMPtr<nsIRunnable> runnable =
     NewRunnableMethod(this, &ServiceWorkerJob::AsyncExecute);
 
   // We may have to wait for the PBackground actor to be initialized
   // before proceeding.  We should always be able to get a ServiceWorkerManager,
   // however, since Start() should not be called during shutdown.
@@ -150,19 +150,19 @@ ServiceWorkerJob::~ServiceWorkerJob()
   MOZ_ASSERT(mState != State::Started);
   MOZ_ASSERT_IF(mState == State::Finished, mResultCallbacksInvoked);
 }
 
 void
 ServiceWorkerJob::InvokeResultCallbacks(ErrorResult& aRv)
 {
   AssertIsOnMainThread();
-  MOZ_ASSERT(mState == State::Started);
+  MOZ_DIAGNOSTIC_ASSERT(mState == State::Started);
 
-  MOZ_ASSERT(!mResultCallbacksInvoked);
+  MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked);
   mResultCallbacksInvoked = true;
 
   nsTArray<RefPtr<Callback>> callbackList;
   callbackList.SwapElements(mResultCallbackList);
 
   for (RefPtr<Callback>& callback : callbackList) {
     // The callback might consume an exception on the ErrorResult, so we need
     // to clone in order to maintain the error for the next callback.
@@ -182,17 +182,24 @@ ServiceWorkerJob::InvokeResultCallbacks(
   ErrorResult converted(aRv);
   InvokeResultCallbacks(converted);
 }
 
 void
 ServiceWorkerJob::Finish(ErrorResult& aRv)
 {
   AssertIsOnMainThread();
-  MOZ_ASSERT(mState == State::Started);
+
+  // Avoid double-completion because it can result on operating on cleaned
+  // up data.  This should not happen, though, so also assert to try to
+  // narrow down the causes.
+  MOZ_DIAGNOSTIC_ASSERT(mState == State::Started);
+  if (mState != State::Started) {
+    return;
+  }
 
   // Ensure that we only surface SecurityErr, TypeErr or InvalidStateErr to script.
   if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) &&
                       !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR) &&
                       !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR)) {
 
     // Remove the old error code so we can replace it with a TypeError.
     aRv.SuppressException();
@@ -208,18 +215,21 @@ ServiceWorkerJob::Finish(ErrorResult& aR
   RefPtr<ServiceWorkerJob> kungFuDeathGrip = this;
 
   if (!mResultCallbacksInvoked) {
     InvokeResultCallbacks(aRv);
   }
 
   mState = State::Finished;
 
-  mFinalCallback->JobFinished(this, aRv);
-  mFinalCallback = nullptr;
+  MOZ_DIAGNOSTIC_ASSERT(mFinalCallback);
+  if (mFinalCallback) {
+    mFinalCallback->JobFinished(this, aRv);
+    mFinalCallback = nullptr;
+  }
 
   // The callback might not consume the error.
   aRv.SuppressException();
 
   // Async release this object to ensure that our caller methods complete
   // as well.
   NS_ReleaseOnMainThread(kungFuDeathGrip.forget(), true /* always proxy */);
 }
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -149,16 +149,30 @@ GPUChild::RecvAccumulateChildHistogram(I
 mozilla::ipc::IPCResult
 GPUChild::RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations)
 {
   Telemetry::AccumulateChildKeyed(GeckoProcessType_GPU, aAccumulations);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+GPUChild::RecvUpdateChildScalars(InfallibleTArray<ScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildScalars(GeckoProcessType_GPU, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+GPUChild::RecvUpdateChildKeyedScalars(InfallibleTArray<KeyedScalarAction>&& aScalarActions)
+{
+  Telemetry::UpdateChildKeyedScalars(GeckoProcessType_GPU, aScalarActions);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 GPUChild::RecvNotifyDeviceReset()
 {
   mHost->mListener->OnProcessDeviceReset(mHost);
   return IPC_OK();
 }
 
 void
 GPUChild::ActorDestroy(ActorDestroyReason aWhy)
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -35,16 +35,18 @@ public:
   void OnVarChanged(const GfxVarUpdate& aVar) override;
 
   // PGPUChild overrides.
   mozilla::ipc::IPCResult RecvInitComplete(const GPUDeviceData& aData) override;
   mozilla::ipc::IPCResult RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog) override;
   mozilla::ipc::IPCResult RecvInitCrashReporter(Shmem&& shmem) override;
   mozilla::ipc::IPCResult RecvAccumulateChildHistogram(InfallibleTArray<Accumulation>&& aAccumulations) override;
   mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
+  mozilla::ipc::IPCResult RecvUpdateChildScalars(InfallibleTArray<ScalarAction>&& aScalarActions) override;
+  mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(InfallibleTArray<KeyedScalarAction>&& aScalarActions) override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
   mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override;
   mozilla::ipc::IPCResult RecvNotifyUiObservers(const nsCString& aTopic) override;
   mozilla::ipc::IPCResult RecvNotifyDeviceReset() override;
 
   static void Destroy(UniquePtr<GPUChild>&& aChild);
 
 private:
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -12,16 +12,18 @@ include protocol PVideoDecoderManager;
 
 using base::ProcessId from "base/process.h";
 using mozilla::TimeDuration from "mozilla/TimeStamp.h";
 using mozilla::CSSToLayoutDeviceScale from "Units.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
 using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 
 namespace mozilla {
 namespace gfx {
 
 union GfxPrefValue {
   bool;
   int32_t;
   uint32_t;
@@ -96,14 +98,16 @@ child:
 
   // Have a message be broadcasted to the UI process by the UI process
   // observer service.
   async NotifyUiObservers(nsCString aTopic);
 
   // Messages for reporting telemetry to the UI process.
   async AccumulateChildHistogram(Accumulation[] accumulations);
   async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+  async UpdateChildScalars(ScalarAction[] actions);
+  async UpdateChildKeyedScalars(KeyedScalarAction[] actions);
 
   async NotifyDeviceReset();
 };
 
 } // namespace gfx
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1321437.js
@@ -0,0 +1,14 @@
+function f(idx) {
+    "use strict";
+    let z = [0, 1, 2, 3, 4, 5, 6, 7, 8, , , ];
+    Object.freeze(z);
+    try {
+        z[idx] = 0;
+    } catch (e) {
+        return e.message;
+    }
+}
+assertEq(f(4), "4 is read-only");
+assertEq(f(-1), 'can\'t define property "-1": Array is not extensible');
+assertEq(f(9), "can't define property 9: Array is not extensible");
+assertEq(f(0xffffffff), 'can\'t define property "4294967295": Array is not extensible');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1330662.js
@@ -0,0 +1,5 @@
+
+for (i=0;i<10000;++i) {
+    a = inIon() ? 0 : 300;
+    buf = new Uint8ClampedArray(a);
+}
--- a/js/src/jit/BacktrackingAllocator.cpp
+++ b/js/src/jit/BacktrackingAllocator.cpp
@@ -414,17 +414,17 @@ BacktrackingAllocator::init()
     }
 
     LiveRegisterSet remainingRegisters(allRegisters_.asLiveSet());
     while (!remainingRegisters.emptyGeneral()) {
         AnyRegister reg = AnyRegister(remainingRegisters.takeAnyGeneral());
         registers[reg.code()].allocatable = true;
     }
     while (!remainingRegisters.emptyFloat()) {
-        AnyRegister reg = AnyRegister(remainingRegisters.takeAnyFloat());
+        AnyRegister reg = AnyRegister(remainingRegisters.takeAnyFloat<RegTypeName::Any>());
         registers[reg.code()].allocatable = true;
     }
 
     LifoAlloc* lifoAlloc = mir->alloc().lifoAlloc();
     for (size_t i = 0; i < AnyRegister::Total; i++) {
         registers[i].reg = AnyRegister::FromCode(i);
         registers[i].allocations.setAllocator(lifoAlloc);
     }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4681,17 +4681,17 @@ CodeGenerator::generateArgumentsChecks(b
     // before entering the function and bailout when arguments don't match.
     // For debug purpose, this is can also be used to force/check that the
     // arguments are correct. Upon fail it will hit a breakpoint.
 
     MIRGraph& mir = gen->graph();
     MResumePoint* rp = mir.entryResumePoint();
 
     // No registers are allocated yet, so it's safe to grab anything.
-    Register temp = GeneralRegisterSet(EntryTempMask).getAny();
+    Register temp = AllocatableGeneralRegisterSet(EntryTempMask).getAny();
 
     const CompileInfo& info = gen->info();
 
     Label miss;
     for (uint32_t i = info.startArgSlot(); i < info.endArgSlot(); i++) {
         // All initial parameters are guaranteed to be MParameters.
         MParameter* param = rp->getOperand(i)->toParameter();
         const TypeSet* types = param->resultTypeSet();
@@ -8457,37 +8457,41 @@ CodeGenerator::emitStoreElementHoleV(T* 
 }
 
 void
 CodeGenerator::visitStoreElementHoleV(LStoreElementHoleV* lir)
 {
     emitStoreElementHoleV(lir);
 }
 
-typedef bool (*ThrowReadOnlyFn)(JSContext*, int32_t);
+typedef bool (*ThrowReadOnlyFn)(JSContext*, HandleObject, int32_t);
 static const VMFunction ThrowReadOnlyInfo =
     FunctionInfo<ThrowReadOnlyFn>(ThrowReadOnlyError, "ThrowReadOnlyError");
 
 void
 CodeGenerator::visitFallibleStoreElementT(LFallibleStoreElementT* lir)
 {
     Register elements = ToRegister(lir->elements());
 
     // Handle frozen objects
     Label isFrozen;
     Address flags(elements, ObjectElements::offsetOfFlags());
     if (!lir->mir()->strict()) {
         masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), &isFrozen);
     } else {
+        Register object = ToRegister(lir->object());
         const LAllocation* index = lir->index();
         OutOfLineCode* ool;
-        if (index->isConstant())
-            ool = oolCallVM(ThrowReadOnlyInfo, lir, ArgList(Imm32(ToInt32(index))), StoreNothing());
-        else
-            ool = oolCallVM(ThrowReadOnlyInfo, lir, ArgList(ToRegister(index)), StoreNothing());
+        if (index->isConstant()) {
+            ool = oolCallVM(ThrowReadOnlyInfo, lir,
+                            ArgList(object, Imm32(ToInt32(index))), StoreNothing());
+        } else {
+            ool = oolCallVM(ThrowReadOnlyInfo, lir,
+                            ArgList(object, ToRegister(index)), StoreNothing());
+        }
         masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), ool->entry());
         // This OOL code should have thrown an exception, so will never return.
         // So, do not bind ool->rejoin() anywhere, so that it implicitly (and without the cost
         // of a jump) does a masm.assumeUnreachable().
     }
 
     emitStoreElementHoleT(lir);
 
@@ -8500,22 +8504,26 @@ CodeGenerator::visitFallibleStoreElement
     Register elements = ToRegister(lir->elements());
 
     // Handle frozen objects
     Label isFrozen;
     Address flags(elements, ObjectElements::offsetOfFlags());
     if (!lir->mir()->strict()) {
         masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), &isFrozen);
     } else {
+        Register object = ToRegister(lir->object());
         const LAllocation* index = lir->index();
         OutOfLineCode* ool;
-        if (index->isConstant())
-            ool = oolCallVM(ThrowReadOnlyInfo, lir, ArgList(Imm32(ToInt32(index))), StoreNothing());
-        else
-            ool = oolCallVM(ThrowReadOnlyInfo, lir, ArgList(ToRegister(index)), StoreNothing());
+        if (index->isConstant()) {
+            ool = oolCallVM(ThrowReadOnlyInfo, lir,
+                            ArgList(object, Imm32(ToInt32(index))), StoreNothing());
+        } else {
+            ool = oolCallVM(ThrowReadOnlyInfo, lir,
+                            ArgList(object, ToRegister(index)), StoreNothing());
+        }
         masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), ool->entry());
         // This OOL code should have thrown an exception, so will never return.
         // So, do not bind ool->rejoin() anywhere, so that it implicitly (and without the cost
         // of a jump) does a masm.assumeUnreachable().
     }
 
     emitStoreElementHoleV(lir);
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -928,17 +928,19 @@ IonCacheIRCompiler::emitLoadDOMExpandoVa
 bool
 IonIC::attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind,
                          HandleScript outerScript)
 {
     // We shouldn't GC or report OOM (or any other exception) here.
     AutoAssertNoPendingException aanpe(cx);
     JS::AutoCheckCannotGC nogc;
 
-    if (writer.failed())
+    // Do nothing if the IR generator failed or triggered a GC that invalidated
+    // the script.
+    if (writer.failed() || !outerScript->hasIonScript())
         return false;
 
     JitContext jctx(cx, nullptr);
     IonCacheIRCompiler compiler(cx, writer, this, outerScript->ionScript());
     if (!compiler.init())
         return false;
 
     JitZone* jitZone = cx->zone()->jitZone();
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -1213,18 +1213,24 @@ Range::sign(TempAllocator& alloc, const 
                             NegativeZeroFlag(op->canBeNegativeZero()),
                             0);
 }
 
 Range*
 Range::NaNToZero(TempAllocator& alloc, const Range *op)
 {
     Range* copy = new(alloc) Range(*op);
-    if (copy->canBeNaN())
+    if (copy->canBeNaN()) {
         copy->max_exponent_ = Range::IncludesInfinity;
+        if (!copy->canBeZero()) {
+            Range zero;
+            zero.setDoubleSingleton(0);
+            copy->unionWith(&zero);
+        }
+    }
     copy->refineToExcludeNegativeZero();
     return copy;
 }
 
 bool
 Range::negativeZeroMul(const Range* lhs, const Range* rhs)
 {
     // The result can only be negative zero if both sides are finite and they
--- a/js/src/jit/RegisterSets.h
+++ b/js/src/jit/RegisterSets.h
@@ -387,32 +387,32 @@ class TypedRegisterSet
 
     void takeRegisterIndex(T reg) {
         bits_ &= ~(SetType(1) << reg.code());
     }
     void takeAllocatable(T reg) {
         bits_ &= ~reg.alignedOrDominatedAliasedSet();
     }
 
-    T getAny() const {
-        // The choice of first or last here is mostly arbitrary, as they are
-        // about the same speed on popular architectures. We choose first, as
-        // it has the advantage of using the "lower" registers more often. These
-        // registers are sometimes more efficient (e.g. optimized encodings for
-        // EAX on x86).
-        return getFirst();
+    static constexpr RegTypeName DefaultType = RegType::DefaultType;
+
+    template <RegTypeName Name>
+    SetType allLive() const {
+        return T::template LiveAsIndexableSet<Name>(bits_);
     }
-    T getFirst() const {
-        MOZ_ASSERT(!empty());
-        return T::FromCode(T::FirstBit(bits_));
+    template <RegTypeName Name>
+    SetType allAllocatable() const {
+        return T::template AllocatableAsIndexableSet<Name>(bits_);
     }
-    T getLast() const {
-        MOZ_ASSERT(!empty());
-        int ireg = T::LastBit(bits_);
-        return T::FromCode(ireg);
+
+    static RegType FirstRegister(SetType set) {
+        return RegType::FromCode(RegType::FirstBit(set));
+    }
+    static RegType LastRegister(SetType set) {
+        return RegType::FromCode(RegType::LastBit(set));
     }
 
     SetType bits() const {
         return bits_;
     }
     uint32_t size() const {
         return T::SetSize(bits_);
     }
@@ -476,16 +476,19 @@ class RegisterSet {
         gpr_.clear();
     }
     bool emptyGeneral() const {
         return gpr_.empty();
     }
     bool emptyFloat() const {
         return fpu_.empty();
     }
+
+    static constexpr RegTypeName DefaultType = RegTypeName::GPR;
+
     constexpr GeneralRegisterSet gprs() const {
         return gpr_;
     }
     GeneralRegisterSet& gprs() {
         return gpr_;
     }
     constexpr FloatRegisterSet fpus() const {
         return fpu_;
@@ -535,16 +538,19 @@ template <typename RegisterSet>
 class AllocatableSet;
 
 template <typename RegisterSet>
 class LiveSet;
 
 // Base accessors classes have the minimal set of raw methods to manipulate the register set
 // given as parameter in a consistent manner.  These methods are:
 //
+//    - all<Type>: Returns a bit-set of all the register of a specific type
+//      which are present.
+//
 //    - has: Returns if all the bits needed to take a register are present.
 //
 //    - takeUnchecked: Subtracts the bits used to represent the register in the
 //      register set.
 //
 //    - addUnchecked: Adds the bits used to represent the register in the
 //      register set.
 
@@ -568,25 +574,35 @@ class AllocatableSetAccessors
   public:
     typedef Set RegSet;
     typedef typename RegSet::RegType RegType;
     typedef typename RegSet::SetType SetType;
 
   protected:
     RegSet set_;
 
+    template <RegTypeName Name>
+    SetType all() const {
+        return set_.template allAllocatable<Name>();
+    }
+
   public:
     AllocatableSetAccessors() : set_() {}
     explicit constexpr AllocatableSetAccessors(SetType set) : set_(set) {}
     explicit constexpr AllocatableSetAccessors(RegSet set) : set_(set) {}
 
     bool has(RegType reg) const {
         return set_.hasAllocatable(reg);
     }
 
+    template <RegTypeName Name>
+    bool hasAny(RegType reg) const {
+        return all<Name>() != 0;
+    }
+
     void addUnchecked(RegType reg) {
         set_.addAllocatable(reg);
     }
 
     void takeUnchecked(RegType reg) {
         set_.takeAllocatable(reg);
     }
 };
@@ -598,16 +614,25 @@ class AllocatableSetAccessors<RegisterSe
   public:
     typedef RegisterSet RegSet;
     typedef AnyRegister RegType;
     typedef char SetType;
 
   protected:
     RegisterSet set_;
 
+    template <RegTypeName Name>
+    GeneralRegisterSet::SetType allGpr() const {
+        return set_.gprs().allAllocatable<Name>();
+    }
+    template <RegTypeName Name>
+    FloatRegisterSet::SetType allFpu() const {
+        return set_.fpus().allAllocatable<Name>();
+    }
+
   public:
     AllocatableSetAccessors() : set_() {}
     explicit constexpr AllocatableSetAccessors(SetType) = delete;
     explicit constexpr AllocatableSetAccessors(RegisterSet set) : set_(set) {}
 
     bool has(Register reg) const {
         return set_.gprs().hasAllocatable(reg);
     }
@@ -650,16 +675,21 @@ class LiveSetAccessors
   public:
     typedef Set RegSet;
     typedef typename RegSet::RegType RegType;
     typedef typename RegSet::SetType SetType;
 
   protected:
     RegSet set_;
 
+    template <RegTypeName Name>
+    SetType all() const {
+        return set_.template allLive<Name>();
+    }
+
   public:
     LiveSetAccessors() : set_() {}
     explicit constexpr LiveSetAccessors(SetType set) : set_(set) {}
     explicit constexpr LiveSetAccessors(RegSet set) : set_(set) {}
 
     bool has(RegType reg) const {
         return set_.hasRegisterIndex(reg);
     }
@@ -680,16 +710,25 @@ class LiveSetAccessors<RegisterSet>
   public:
     typedef RegisterSet RegSet;
     typedef AnyRegister RegType;
     typedef char SetType;
 
   protected:
     RegisterSet set_;
 
+    template <RegTypeName Name>
+    GeneralRegisterSet::SetType allGpr() const {
+        return set_.gprs().allLive<Name>();
+    }
+    template <RegTypeName Name>
+    FloatRegisterSet::SetType allFpu() const {
+        return set_.fpus().allLive<Name>();
+    }
+
   public:
     LiveSetAccessors() : set_() {}
     explicit constexpr LiveSetAccessors(SetType) = delete;
     explicit constexpr LiveSetAccessors(RegisterSet set) : set_(set) {}
 
     bool has(Register reg) const {
         return set_.gprs().hasRegisterIndex(reg);
     }
@@ -746,72 +785,94 @@ class SpecializedRegSet : public Accesso
     }
 
     using Parent::takeUnchecked;
     void take(RegType reg) {
         MOZ_ASSERT(has(reg));
         takeUnchecked(reg);
     }
 
-    RegType getAny() const {
-        return this->Parent::set_.getAny();
-    }
-    RegType getFirst() const {
-        return this->Parent::set_.getFirst();
-    }
-    RegType getLast() const {
-        return this->Parent::set_.getLast();
+    template <RegTypeName Name>
+    bool hasAny() const {
+        return Parent::template all<Name>() != 0;
     }
 
+    template <RegTypeName Name = RegSet::DefaultType>
+    RegType getFirst() const {
+        SetType set = Parent::template all<Name>();
+        MOZ_ASSERT(set);
+        return RegSet::FirstRegister(set);
+    }
+    template <RegTypeName Name = RegSet::DefaultType>
+    RegType getLast() const {
+        SetType set = Parent::template all<Name>();
+        MOZ_ASSERT(set);
+        return RegSet::LastRegister(set);
+    }
+    template <RegTypeName Name = RegSet::DefaultType>
+    RegType getAny() const {
+        // The choice of first or last here is mostly arbitrary, as they are
+        // about the same speed on popular architectures. We choose first, as
+        // it has the advantage of using the "lower" registers more often. These
+        // registers are sometimes more efficient (e.g. optimized encodings for
+        // EAX on x86).
+        return getFirst<Name>();
+    }
+
+    template <RegTypeName Name = RegSet::DefaultType>
     RegType getAnyExcluding(RegType preclude) {
         if (!has(preclude))
-            return getAny();
+            return getAny<Name>();
 
         take(preclude);
-        RegType result = getAny();
+        RegType result = getAny<Name>();
         add(preclude);
         return result;
     }
 
+    template <RegTypeName Name = RegSet::DefaultType>
     RegType takeAny() {
-        RegType reg = getAny();
+        RegType reg = getAny<Name>();
         take(reg);
         return reg;
     }
+    template <RegTypeName Name = RegSet::DefaultType>
     RegType takeFirst() {
-        RegType reg = getFirst();
+        RegType reg = getFirst<Name>();
         take(reg);
         return reg;
     }
+    template <RegTypeName Name = RegSet::DefaultType>
     RegType takeLast() {
-        RegType reg = getLast();
+        RegType reg = getLast<Name>();
         take(reg);
         return reg;
     }
 
     ValueOperand takeAnyValue() {
 #if defined(JS_NUNBOX32)
-        return ValueOperand(takeAny(), takeAny());
+        return ValueOperand(takeAny<RegTypeName::GPR>(), takeAny<RegTypeName::GPR>());
 #elif defined(JS_PUNBOX64)
-        return ValueOperand(takeAny());
+        return ValueOperand(takeAny<RegTypeName::GPR>());
 #else
 #error "Bad architecture"
 #endif
     }
 
     bool aliases(ValueOperand v) const {
 #ifdef JS_NUNBOX32
         return has(v.typeReg()) || has(v.payloadReg());
 #else
         return has(v.valueReg());
 #endif
     }
 
+    template <RegTypeName Name = RegSet::DefaultType>
     RegType takeAnyExcluding(RegType preclude) {
-        RegType reg = getAnyExcluding(preclude);
+        RegType reg = getAnyExcluding<Name>(preclude);
         take(reg);
         return reg;
     }
 };
 
 // Specialization of the accessors for the RegisterSet aggregate.
 template <class Accessors>
 class SpecializedRegSet<Accessors, RegisterSet> : public Accessors
@@ -836,22 +897,27 @@ class SpecializedRegSet<Accessors, Regis
 
     bool emptyGeneral() const {
         return this->Parent::set_.emptyGeneral();
     }
     bool emptyFloat() const {
         return this->Parent::set_.emptyFloat();
     }
 
-
     using Parent::has;
     bool has(AnyRegister reg) const {
         return reg.isFloat() ? has(reg.fpu()) : has(reg.gpr());
     }
 
+    template <RegTypeName Name>
+    bool hasAny() const {
+        if (Name == RegTypeName::GPR)
+            return Parent::template allGpr<RegTypeName::GPR>() != 0;
+        return Parent::template allFpu<Name>() != 0;
+    }
 
     using Parent::addUnchecked;
     void addUnchecked(AnyRegister reg) {
         if (reg.isFloat())
             addUnchecked(reg.fpu());
         else
             addUnchecked(reg.gpr());
     }
@@ -890,29 +956,35 @@ class SpecializedRegSet<Accessors, Regis
     void take(AnyRegister reg) {
         if (reg.isFloat())
             take(reg.fpu());
         else
             take(reg.gpr());
     }
 
     Register getAnyGeneral() const {
-        return this->Parent::set_.gprs().getAny();
+        GeneralRegisterSet::SetType set = Parent::template allGpr<RegTypeName::GPR>();
+        MOZ_ASSERT(set);
+        return GeneralRegisterSet::FirstRegister(set);
     }
+    template <RegTypeName Name = RegTypeName::Float64>
     FloatRegister getAnyFloat() const {
-        return this->Parent::set_.fpus().getAny();
+        FloatRegisterSet::SetType set = Parent::template allFpu<Name>();
+        MOZ_ASSERT(set);
+        return FloatRegisterSet::FirstRegister(set);
     }
 
     Register takeAnyGeneral() {
         Register reg = getAnyGeneral();
         take(reg);
         return reg;
     }
+    template <RegTypeName Name = RegTypeName::Float64>
     FloatRegister takeAnyFloat() {
-        FloatRegister reg = getAnyFloat();
+        FloatRegister reg = getAnyFloat<Name>();
         take(reg);
         return reg;
     }
     ValueOperand takeAnyValue() {
 #if defined(JS_NUNBOX32)
         return ValueOperand(takeAnyGeneral(), takeAnyGeneral());
 #elif defined(JS_PUNBOX64)
         return ValueOperand(takeAnyGeneral());
@@ -1100,21 +1172,21 @@ class TypedRegisterIterator
     { }
     TypedRegisterIterator(const TypedRegisterIterator& other) : regset_(other.regset_)
     { }
 
     bool more() const {
         return !regset_.empty();
     }
     TypedRegisterIterator<T>& operator ++() {
-        regset_.takeAny();
+        regset_.template takeAny<RegTypeName::Any>();
         return *this;
     }
     T operator*() const {
-        return regset_.getAny();
+        return regset_.template getAny<RegTypeName::Any>();
     }
 };
 
 // iterates backwards, that is, rn to r0
 template <typename T>
 class TypedRegisterBackwardIterator
 {
     LiveSet<TypedRegisterSet<T>> regset_;
@@ -1127,21 +1199,21 @@ class TypedRegisterBackwardIterator
     TypedRegisterBackwardIterator(const TypedRegisterBackwardIterator& other)
       : regset_(other.regset_)
     { }
 
     bool more() const {
         return !regset_.empty();
     }
     TypedRegisterBackwardIterator<T>& operator ++() {
-        regset_.takeLast();
+        regset_.template takeLast<RegTypeName::Any>();
         return *this;
     }
     T operator*() const {
-        return regset_.getLast();
+        return regset_.template getLast<RegTypeName::Any>();
     }
 };
 
 // iterates forwards, that is r0 to rn
 template <typename T>
 class TypedRegisterForwardIterator
 {
     LiveSet<TypedRegisterSet<T>> regset_;
@@ -1153,21 +1225,21 @@ class TypedRegisterForwardIterator
     { }
     TypedRegisterForwardIterator(const TypedRegisterForwardIterator& other) : regset_(other.regset_)
     { }
 
     bool more() const {
         return !regset_.empty();
     }
     TypedRegisterForwardIterator<T>& operator ++() {
-        regset_.takeFirst();
+        regset_.template takeFirst<RegTypeName::Any>();
         return *this;
     }
     T operator*() const {
-        return regset_.getFirst();
+        return regset_.template getFirst<RegTypeName::Any>();
     }
 };
 
 typedef TypedRegisterIterator<Register> GeneralRegisterIterator;
 typedef TypedRegisterIterator<FloatRegister> FloatRegisterIterator;
 typedef TypedRegisterBackwardIterator<Register> GeneralRegisterBackwardIterator;
 typedef TypedRegisterBackwardIterator<FloatRegister> FloatRegisterBackwardIterator;
 typedef TypedRegisterForwardIterator<Register> GeneralRegisterForwardIterator;
--- a/js/src/jit/Registers.h
+++ b/js/src/jit/Registers.h
@@ -83,27 +83,58 @@ struct Register {
         MOZ_ASSERT(aliasIdx == 0);
         *ret = *this;
     }
 
     SetType alignedOrDominatedAliasedSet() const {
         return SetType(1) << code();
     }
 
+    static constexpr RegTypeName DefaultType = RegTypeName::GPR;
+
+    template <RegTypeName = DefaultType>
+    static SetType LiveAsIndexableSet(SetType s) {
+        return SetType(0);
+    }
+
+    template <RegTypeName Name = DefaultType>
+    static SetType AllocatableAsIndexableSet(SetType s) {
+        static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable");
+        return SetType(0);
+    }
+
     static uint32_t SetSize(SetType x) {
         return Codes::SetSize(x);
     }
     static uint32_t FirstBit(SetType x) {
         return Codes::FirstBit(x);
     }
     static uint32_t LastBit(SetType x) {
         return Codes::LastBit(x);
     }
 };
 
+template <> inline Register::SetType
+Register::LiveAsIndexableSet<RegTypeName::GPR>(SetType set)
+{
+    return set;
+}
+
+template <> inline Register::SetType
+Register::LiveAsIndexableSet<RegTypeName::Any>(SetType set)
+{
+    return set;
+}
+
+template <> inline Register::SetType
+Register::AllocatableAsIndexableSet<RegTypeName::GPR>(SetType set)
+{
+    return set;
+}
+
 #if defined(JS_NUNBOX32)
 static const uint32_t INT64LOW_OFFSET = 0 * sizeof(int32_t);
 static const uint32_t INT64HIGH_OFFSET = 1 * sizeof(int32_t);
 #endif
 
 struct Register64
 {
 #ifdef JS_PUNBOX64
--- a/js/src/jit/StupidAllocator.cpp
+++ b/js/src/jit/StupidAllocator.cpp
@@ -76,17 +76,18 @@ StupidAllocator::init()
     // Assign physical registers to the tracked allocation.
     {
         registerCount = 0;
         LiveRegisterSet remainingRegisters(allRegisters_.asLiveSet());
         while (!remainingRegisters.emptyGeneral())
             registers[registerCount++].reg = AnyRegister(remainingRegisters.takeAnyGeneral());
 
         while (!remainingRegisters.emptyFloat())
-            registers[registerCount++].reg = AnyRegister(remainingRegisters.takeAnyFloat());
+            registers[registerCount++].reg =
+                AnyRegister(remainingRegisters.takeAnyFloat<RegTypeName::Any>());
 
         MOZ_ASSERT(registerCount <= MAX_REGISTERS);
     }
 
     return true;
 }
 
 bool
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1313,20 +1313,31 @@ ThrowRuntimeLexicalError(JSContext* cx, 
 {
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
     ReportRuntimeLexicalError(cx, errorNumber, script, iter.pc());
     return false;
 }
 
 bool
-ThrowReadOnlyError(JSContext* cx, int32_t index)
+ThrowReadOnlyError(JSContext* cx, HandleObject obj, int32_t index)
 {
-    RootedValue val(cx, Int32Value(index));
-    ReportValueError(cx, JSMSG_READ_ONLY, JSDVG_IGNORE_STACK, val, nullptr);
+    // We have to throw different errors depending on whether |index| is past
+    // the array length, etc. It's simpler to just call SetProperty to ensure
+    // we match the interpreter.
+
+    RootedValue objVal(cx, ObjectValue(*obj));
+    RootedValue indexVal(cx, Int32Value(index));
+    RootedId id(cx);
+    if (!ValueToId<CanGC>(cx, indexVal, &id))
+        return false;
+
+    ObjectOpResult result;
+    MOZ_ALWAYS_FALSE(SetProperty(cx, obj, id, UndefinedHandleValue, objVal, result) &&
+                     result.checkStrictErrorOrWarning(cx, obj, id, /* strict = */ true));
     return false;
 }
 
 bool
 ThrowBadDerivedReturn(JSContext* cx, HandleValue v)
 {
     ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, v, nullptr);
     return false;
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -792,17 +792,17 @@ IonMarkFunction(MIRType type)
 
 bool ObjectIsCallable(JSObject* obj);
 bool ObjectIsConstructor(JSObject* obj);
 
 MOZ_MUST_USE bool
 ThrowRuntimeLexicalError(JSContext* cx, unsigned errorNumber);
 
 MOZ_MUST_USE bool
-ThrowReadOnlyError(JSContext* cx, int32_t index);
+ThrowReadOnlyError(JSContext* cx, HandleObject obj, int32_t index);
 
 MOZ_MUST_USE bool
 BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame);
 
 MOZ_MUST_USE bool
 ThrowBadDerivedReturn(JSContext* cx, HandleValue v);
 
 MOZ_MUST_USE bool
--- a/js/src/jit/arm/Architecture-arm.h
+++ b/js/src/jit/arm/Architecture-arm.h
@@ -7,16 +7,18 @@
 #ifndef jit_arm_Architecture_arm_h
 #define jit_arm_Architecture_arm_h
 
 #include "mozilla/MathAlgorithms.h"
 
 #include <limits.h>
 #include <stdint.h>
 
+#include "jit/shared/Architecture-shared.h"
+
 #include "js/Utility.h"
 
 // GCC versions 4.6 and above define __ARM_PCS_VFP to denote a hard-float
 // ABI target. The iOS toolchain doesn't define anything specific here,
 // but iOS always supports VFP.
 #if defined(__ARM_PCS_VFP) || defined(XP_IOS)
 #define JS_CODEGEN_ARM_HARDFP
 #endif
@@ -280,16 +282,47 @@ class FloatRegisters
     static const uint32_t Total = 48;
     static const uint32_t TotalDouble = 16;
     static const uint32_t TotalSingle = 32;
     static const uint32_t Allocatable = 45;
     // There are only 32 places that we can put values.
     static const uint32_t TotalPhys = 32;
     static uint32_t ActualTotalPhys();
 
+    // ARM float registers overlap in a way that for 1 double registers, in the
+    // range d0-d15, we have 2 singles register in the range s0-s31. d16-d31
+    // have no single register aliases.  The aliasing rule state that d{n}
+    // aliases s{2n} and s{2n+1}, for n in [0 .. 15].
+    //
+    // The register set is used to represent either allocatable register or live
+    // registers. The register maps d0-d15 and s0-s31 to a single bit each. The
+    // registers d16-d31 are not used at the moment.
+    //
+    // uuuu uuuu uuuu uuuu dddd dddd dddd dddd ssss ssss ssss ssss ssss ssss ssss ssss
+    //                     ^                 ^ ^                                     ^
+    //                     '-- d15      d0 --' '-- s31                          s0 --'
+    //
+    // LiveSet are handled by adding the bit of each register without
+    // considering the aliases.
+    //
+    // AllocatableSet are handled by adding and removing the bit of each
+    // aligned-or-dominated-aliased registers.
+    //
+    //     ...0...00... : s{2n}, s{2n+1} and d{n} are not available
+    //     ...1...01... : s{2n} is available (*)
+    //     ...0...10... : s{2n+1} is available
+    //     ...1...11... : s{2n}, s{2n+1} and d{n} are available
+    //
+    // (*) Note that d{n} bit is set, but is not available because s{2n+1} bit
+    // is not set, which is required as d{n} dominates s{2n+1}. The d{n} bit is
+    // set, because s{2n} is aligned.
+    //
+    //        |        d{n}       |
+    //        | s{2n+1} |  s{2n}  |
+    //
     typedef uint64_t SetType;
     static const SetType AllSingleMask = (1ull << TotalSingle) - 1;
     static const SetType AllDoubleMask = ((1ull << TotalDouble) - 1) << TotalSingle;
     static const SetType AllMask = AllDoubleMask | AllSingleMask;
 
     // d15 is the ScratchFloatReg.
     static const SetType NonVolatileDoubleMask =
          ((1ULL << d8) |
@@ -352,30 +385,27 @@ class VFPRegister
     };
 
     typedef FloatRegisters Codes;
     typedef Codes::Code Code;
     typedef Codes::Encoding Encoding;
 
   protected:
     RegType kind : 2;
-    // ARM doesn't have more than 32 registers. Don't take more bits than we'll
-    // need. Presently, we don't have plans to address the upper and lower
-    // halves of the double registers seprately, so 5 bits should suffice. If we
-    // do decide to address them seprately (vmov, I'm looking at you), we will
-    // likely specify it as a separate field.
   public:
+    // ARM doesn't have more than 32 registers of each type, so 5 bits should
+    // suffice.
     uint32_t code_ : 5;
   protected:
     bool _isInvalid : 1;
     bool _isMissing : 1;
 
   public:
     constexpr VFPRegister(uint32_t r, RegType k)
-      : kind(k), code_ (Code(r)), _isInvalid(false), _isMissing(false)
+      : kind(k), code_(Code(r)), _isInvalid(false), _isMissing(false)
     { }
     constexpr VFPRegister()
       : kind(Double), code_(Code(0)), _isInvalid(true), _isMissing(false)
     { }
 
     constexpr VFPRegister(RegType k, uint32_t id, bool invalid, bool missing) :
         kind(k), code_(Code(id)), _isInvalid(invalid), _isMissing(missing) {
     }
@@ -528,17 +558,17 @@ class VFPRegister
     // This function is used to ensure that Register set can take all Single
     // registers, even if we are taking a mix of either double or single
     // registers.
     //
     //   s0.alignedOrDominatedAliasedSet() == s0 | d0.
     //   s1.alignedOrDominatedAliasedSet() == s1.
     //   d0.alignedOrDominatedAliasedSet() == s0 | s1 | d0.
     //
-    // This way the Allocator register set does not have to do any arithmetics
+    // This way the Allocatable register set does not have to do any arithmetics
     // to know if a register is available or not, as we have the following
     // relations:
     //
     //   d0.alignedOrDominatedAliasedSet() ==
     //       s0.alignedOrDominatedAliasedSet() | s1.alignedOrDominatedAliasedSet()
     //
     //   s0.alignedOrDominatedAliasedSet() & s1.alignedOrDominatedAliasedSet() == 0
     //
@@ -548,16 +578,29 @@ class VFPRegister
                 return SetType(1) << code_;
             return (SetType(1) << code_) | (SetType(1) << (32 + code_ / 2));
         }
 
         MOZ_ASSERT(isDouble());
         return (SetType(0b11) << (code_ * 2)) | (SetType(1) << (32 + code_));
     }
 
+    static constexpr RegTypeName DefaultType = RegTypeName::Float64;
+
+    template <RegTypeName = DefaultType>
+    static SetType LiveAsIndexableSet(SetType s) {
+        return SetType(0);
+    }
+
+    template <RegTypeName Name = DefaultType>
+    static SetType AllocatableAsIndexableSet(SetType s) {
+        static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable");
+        return SetType(0);
+    }
+
     static uint32_t SetSize(SetType x) {
         static_assert(sizeof(SetType) == 8, "SetType must be 64 bits");
         return mozilla::CountPopulation32(x);
     }
     static Code FromName(const char* name) {
         return FloatRegisters::FromName(name);
     }
     static TypedRegisterSet<VFPRegister> ReduceSetForPush(const TypedRegisterSet<VFPRegister>& s);
@@ -567,16 +610,89 @@ class VFPRegister
         return mozilla::CountTrailingZeroes64(x);
     }
     static uint32_t LastBit(SetType x) {
         return 63 - mozilla::CountLeadingZeroes64(x);
     }
 
 };
 
+template <> inline VFPRegister::SetType
+VFPRegister::LiveAsIndexableSet<RegTypeName::Float32>(SetType set)
+{
+    return set & FloatRegisters::AllSingleMask;
+}
+
+template <> inline VFPRegister::SetType
+VFPRegister::LiveAsIndexableSet<RegTypeName::Float64>(SetType set)
+{
+    return set & FloatRegisters::AllDoubleMask;
+}
+
+template <> inline VFPRegister::SetType
+VFPRegister::LiveAsIndexableSet<RegTypeName::Any>(SetType set)
+{
+    return set;
+}
+
+template <> inline VFPRegister::SetType
+VFPRegister::AllocatableAsIndexableSet<RegTypeName::Float32>(SetType set)
+{
+    // Single registers are not dominating any smaller registers, thus masking
+    // is enough to convert an allocatable set into a set of register list all
+    // single register available.
+    return set & FloatRegisters::AllSingleMask;
+}
+
+template <> inline VFPRegister::SetType
+VFPRegister::AllocatableAsIndexableSet<RegTypeName::Float64>(SetType set)
+{
+    // An allocatable float register set is represented as follow:
+    //
+    // uuuu uuuu uuuu uuuu dddd dddd dddd dddd ssss ssss ssss ssss ssss ssss ssss ssss
+    //                     ^                 ^ ^                                     ^
+    //                     '-- d15      d0 --' '-- s31                          s0 --'
+    //
+    //     ...0...00... : s{2n}, s{2n+1} and d{n} are not available
+    //     ...1...01... : s{2n} is available
+    //     ...0...10... : s{2n+1} is available
+    //     ...1...11... : s{2n}, s{2n+1} and d{n} are available
+    //
+    // The goal of this function is to return the set of double registers which
+    // are available as an indexable bit set. This implies that iff a double bit
+    // is set in the returned set, then the register is available.
+    //
+    // To do so, this functions converts the 32 bits set of single registers
+    // into a 16 bits set of equivalent double registers. Then, we mask out
+    // double registers which do not have all the single register that compose
+    // them. As d{n} bit is set when s{2n} is available, we only need to take
+    // s{2n+1} into account.
+
+    // Convert  s7s6s5s4 s3s2s1s0  into  s7s5s3s1, for all s0-s31.
+    SetType s2d = AllocatableAsIndexableSet<RegTypeName::Float32>(set);
+    static_assert(FloatRegisters::TotalSingle == 32, "Wrong mask");
+    s2d = (0xaaaaaaaa & s2d) >> 1; // Filter s{2n+1} registers.
+    // Group adjacent bits as follow:
+    //     0.0.s3.s1 == ((0.s3.0.s1) >> 1 | (0.s3.0.s1)) & 0b0011;
+    s2d = ((s2d >> 1) | s2d) & 0x33333333; // 0a0b --> 00ab
+    s2d = ((s2d >> 2) | s2d) & 0x0f0f0f0f; // 00ab00cd --> 0000abcd
+    s2d = ((s2d >> 4) | s2d) & 0x00ff00ff;
+    s2d = ((s2d >> 8) | s2d) & 0x0000ffff;
+    // Move the s7s5s3s1 to the aliased double positions.
+    s2d = s2d << FloatRegisters::TotalSingle;
+
+    // Note: We currently do not use any representation for d16-d31.
+    static_assert(FloatRegisters::TotalDouble == 16,
+        "d16-d31 do not have a single register mapping");
+
+    // Filter out any double register which are not allocatable due to
+    // non-aligned dominated single registers.
+    return set & s2d;
+}
+
 // The only floating point register set that we work with are the VFP Registers.
 typedef VFPRegister FloatRegister;
 
 uint32_t GetARMFlags();
 bool HasARMv7();
 bool HasMOVWT();
 bool HasLDSTREXBHD();           // {LD,ST}REX{B,H,D}
 bool HasDMBDSBISB();            // DMB, DSB, and ISB
--- a/js/src/jit/arm64/Architecture-arm64.h
+++ b/js/src/jit/arm64/Architecture-arm64.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_arm64_Architecture_arm64_h
 #define jit_arm64_Architecture_arm64_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/MathAlgorithms.h"
 
+#include "jit/shared/Architecture-shared.h"
+
 #include "js/Utility.h"
 
 namespace js {
 namespace jit {
 
 // AArch64 has 32 64-bit integer registers, x0 though x31.
 //  x31 is special and functions as both the stack pointer and a zero register.
 //  The bottom 32 bits of each of the X registers is accessible as w0 through w31.
@@ -264,18 +266,18 @@ class FloatRegisters
                 (1 << FloatRegisters::d20) | (1 << FloatRegisters::d21) |
                 (1 << FloatRegisters::d22) | (1 << FloatRegisters::d23) |
                 (1 << FloatRegisters::d24) | (1 << FloatRegisters::d25) |
                 (1 << FloatRegisters::d26) | (1 << FloatRegisters::d27) |
                 (1 << FloatRegisters::d28) | (1 << FloatRegisters::d29) |
                 (1 << FloatRegisters::d30)) * SpreadCoefficient;
 
     static const SetType VolatileMask = AllMask & ~NonVolatileMask;
-    static const SetType AllDoubleMask = AllMask;
-    static const SetType AllSingleMask = AllMask;
+    static const SetType AllDoubleMask = AllPhysMask << TotalPhys;
+    static const SetType AllSingleMask = AllPhysMask;
 
     static const SetType WrapperMask = VolatileMask;
 
     // d31 is the ScratchFloatReg.
     static const SetType NonAllocatableMask = (SetType(1) << FloatRegisters::d31) * SpreadCoefficient;
 
     // Registers that can be allocated without being saved, generally.
     static const SetType TempMask = VolatileMask & ~NonAllocatableMask;
@@ -399,17 +401,17 @@ struct FloatRegister
     }
     constexpr uint32_t size() const {
         return k_ == FloatRegisters::Double ? sizeof(double) : sizeof(float);
     }
     uint32_t numAlignedAliased() {
         return numAliased();
     }
     void alignedAliased(uint32_t aliasIdx, FloatRegister* ret) {
-        MOZ_ASSERT(aliasIdx == 0);
+        MOZ_ASSERT(aliasIdx < numAliased());
         aliased(aliasIdx, ret);
     }
     SetType alignedOrDominatedAliasedSet() const {
         return Codes::SpreadCoefficient << code_;
     }
 
     bool isSingle() const {
         return k_ == FloatRegisters::Single;
@@ -425,26 +427,57 @@ struct FloatRegister
         JS_STATIC_ASSERT(sizeof(SetType) == 8);
         return mozilla::CountTrailingZeroes64(x);
     }
     static uint32_t LastBit(SetType x) {
         JS_STATIC_ASSERT(sizeof(SetType) == 8);
         return 63 - mozilla::CountLeadingZeroes64(x);
     }
 
+    static constexpr RegTypeName DefaultType = RegTypeName::Float64;
+
+    template <RegTypeName Name = DefaultType>
+    static SetType LiveAsIndexableSet(SetType s) {
+        return SetType(0);
+    }
+
+    template <RegTypeName Name = DefaultType>
+    static SetType AllocatableAsIndexableSet(SetType s) {
+        static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable");
+        return LiveAsIndexableSet<Name>(s);
+    }
+
     static TypedRegisterSet<FloatRegister> ReduceSetForPush(const TypedRegisterSet<FloatRegister>& s);
     static uint32_t GetSizeInBytes(const TypedRegisterSet<FloatRegister>& s);
     static uint32_t GetPushSizeInBytes(const TypedRegisterSet<FloatRegister>& s);
     uint32_t getRegisterDumpOffsetInBytes();
 
   public:
     Code code_ : 8;
     FloatRegisters::Kind k_ : 1;
 };
 
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Float32>(SetType set)
+{
+    return set & FloatRegisters::AllSingleMask;
+}
+
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Float64>(SetType set)
+{
+    return set & FloatRegisters::AllDoubleMask;
+}
+
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Any>(SetType set)
+{
+    return set;
+}
+
 // ARM/D32 has double registers that cannot be treated as float32.
 // Luckily, ARMv8 doesn't have the same misfortune.
 inline bool
 hasUnaliasedDouble()
 {
     return false;
 }
 
--- a/js/src/jit/mips-shared/Architecture-mips-shared.h
+++ b/js/src/jit/mips-shared/Architecture-mips-shared.h
@@ -7,16 +7,18 @@
 #ifndef jit_mips_shared_Architecture_mips_shared_h
 #define jit_mips_shared_Architecture_mips_shared_h
 
 #include "mozilla/MathAlgorithms.h"
 
 #include <limits.h>
 #include <stdint.h>
 
+#include "jit/shared/Architecture-shared.h"
+
 #include "js/Utility.h"
 
 // gcc appears to use _mips_hard_float to denote
 // that the target is a hard-float target.
 #ifdef _mips_hard_float
 #define JS_CODEGEN_MIPS_HARDFP
 #endif
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit/shared/Architecture-shared.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef jit_shared_Architecture_shared_h
+#define jit_shared_Architecture_shared_h
+
+namespace js {
+namespace jit {
+
+enum class RegTypeName {
+    GPR,
+    Float32,
+    Float64,
+    Vector128,
+    Any
+};
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_shared_Architecture_shared_h */
--- a/js/src/jit/x86-shared/Architecture-x86-shared.h
+++ b/js/src/jit/x86-shared/Architecture-x86-shared.h
@@ -10,16 +10,18 @@
 #if !defined(JS_CODEGEN_X86) && !defined(JS_CODEGEN_X64)
 # error "Unsupported architecture!"
 #endif
 
 #include "mozilla/MathAlgorithms.h"
 
 #include <string.h>
 
+#include "jit/shared/Architecture-shared.h"
+
 #include "jit/x86-shared/Constants-x86-shared.h"
 
 namespace js {
 namespace jit {
 
 // Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
 static constexpr bool SupportsUint32x4FloatConversions = false;
 
@@ -257,16 +259,17 @@ class FloatRegisters {
     static const SetType SpreadScalar = SpreadSingle | SpreadDouble;
     static const SetType SpreadVector = SpreadSimd128;
     static const SetType Spread = SpreadScalar | SpreadVector;
 
     static const SetType AllPhysMask = ((1 << TotalPhys) - 1);
     static const SetType AllMask = AllPhysMask * Spread;
     static const SetType AllDoubleMask = AllPhysMask * SpreadDouble;
     static const SetType AllSingleMask = AllPhysMask * SpreadSingle;
+    static const SetType AllVector128Mask = AllPhysMask * SpreadSimd128;
 
 #if defined(JS_CODEGEN_X86)
     static const SetType NonAllocatableMask =
         Spread * (1 << X86Encoding::xmm7);     // This is ScratchDoubleReg.
 
 #elif defined(JS_CODEGEN_X64)
     static const SetType NonAllocatableMask =
         Spread * (1 << X86Encoding::xmm15);    // This is ScratchDoubleReg.
@@ -431,21 +434,58 @@ struct FloatRegister {
     void alignedAliased(uint32_t aliasIdx, FloatRegister* ret) const {
         aliased(aliasIdx, ret);
     }
 
     SetType alignedOrDominatedAliasedSet() const {
         return Codes::Spread << reg_;
     }
 
+    static constexpr RegTypeName DefaultType = RegTypeName::Float64;
+
+    template <RegTypeName = DefaultType>
+    static SetType LiveAsIndexableSet(SetType s) {
+        return SetType(0);
+    }
+
+    template <RegTypeName Name = DefaultType>
+    static SetType AllocatableAsIndexableSet(SetType s) {
+        static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable");
+        return LiveAsIndexableSet<Name>(s);
+    }
+
     static TypedRegisterSet<FloatRegister> ReduceSetForPush(const TypedRegisterSet<FloatRegister>& s);
     static uint32_t GetPushSizeInBytes(const TypedRegisterSet<FloatRegister>& s);
     uint32_t getRegisterDumpOffsetInBytes();
 };
 
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Float32>(SetType set)
+{
+    return set & FloatRegisters::AllSingleMask;
+}
+
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Float64>(SetType set)
+{
+    return set & FloatRegisters::AllDoubleMask;
+}
+
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Vector128>(SetType set)
+{
+    return set & FloatRegisters::AllVector128Mask;
+}
+
+template <> inline FloatRegister::SetType
+FloatRegister::LiveAsIndexableSet<RegTypeName::Any>(SetType set)
+{
+    return set;
+}
+
 // Arm/D32 has double registers that can NOT be treated as float32
 // and this requires some dances in lowering.
 inline bool
 hasUnaliasedDouble()
 {
     return false;
 }
 
--- a/js/src/jsapi-tests/testJitRegisterSet.cpp
+++ b/js/src/jsapi-tests/testJitRegisterSet.cpp
@@ -30,16 +30,22 @@ CoPrime(size_t a, size_t b)
             continue;                                       \
         for (size_t start = 0; start < RegTotal; start++) { \
             size_t index = start;
 
 #define END_INDEX_WALK                                      \
         }                                                   \
     }
 
+#define BEGIN_All_WALK(RegTotal)                            \
+    static const size_t Total = RegTotal;                   \
+    size_t walk = 1;                                        \
+    size_t start = 0;                                       \
+    size_t index = start;
+
 #define FOR_ALL_REGISTERS(Register, reg)            \
     do {                                            \
         Register reg = Register::FromCode(index);
 
 #define END_FOR_ALL_REGISTERS                    \
         index = (index + walk) % Total;          \
     } while(index != start)
 
@@ -131,8 +137,75 @@ BEGIN_TEST(testJitRegisterSet_FPU)
 
     CHECK(liveRegs.empty());
     CHECK(pool.set() == FloatRegisterSet::All());
 
     END_INDEX_WALK
     return true;
 }
 END_TEST(testJitRegisterSet_FPU)
+
+void pullAllFpus(AllocatableFloatRegisterSet& set, uint32_t& max_bits, uint32_t bits) {
+    FloatRegisterSet allocSet(set.bits());
+    FloatRegisterSet available_f32(allocSet.allAllocatable<RegTypeName::Float32>());
+    FloatRegisterSet available_f64(allocSet.allAllocatable<RegTypeName::Float64>());
+    FloatRegisterSet available_v128(allocSet.allAllocatable<RegTypeName::Vector128>());
+    for (FloatRegisterIterator it(available_f32); it.more(); ++it) {
+        FloatRegister tmp = *it;
+        set.take(tmp);
+        pullAllFpus(set, max_bits, bits + 32);
+        set.add(tmp);
+    }
+    for (FloatRegisterIterator it(available_f64); it.more(); ++it) {
+        FloatRegister tmp = *it;
+        set.take(tmp);
+        pullAllFpus(set, max_bits, bits + 64);
+        set.add(tmp);
+    }
+    for (FloatRegisterIterator it(available_v128); it.more(); ++it) {
+        FloatRegister tmp = *it;
+        set.take(tmp);
+        pullAllFpus(set, max_bits, bits + 128);
+        set.add(tmp);
+    }
+    if (bits >= max_bits)
+        max_bits = bits;
+}
+
+BEGIN_TEST(testJitRegisterSet_FPU_Aliases)
+{
+    BEGIN_All_WALK(FloatRegisters::Total);
+    FOR_ALL_REGISTERS(FloatRegister, reg) {
+        AllocatableFloatRegisterSet pool;
+        pool.add(reg);
+
+        uint32_t alias_bits = 0;
+        for (uint32_t i = 0; i < reg.numAlignedAliased(); i++) {
+            FloatRegister alias;
+            reg.alignedAliased(i, &alias);
+
+            if (alias.isSingle()) {
+                if (alias_bits <= 32)
+                    alias_bits = 32;
+            } else if (alias.isDouble()) {
+                if (alias_bits <= 64)
+                    alias_bits = 64;
+            } else if (alias.isSimd128()) {
+                if (alias_bits <= 128)
+                    alias_bits = 128;
+            }
+        }
+
+        uint32_t max_bits = 0;
+        pullAllFpus(pool, max_bits, 0);
+
+        // By adding one register, we expect that we should not be able to pull
+        // more than any of its aligned aliases.  This rule should hold for both
+        // x64 and ARM.
+        CHECK(max_bits <= alias_bits);
+
+        // We added one register, we expect to be able to pull it back.
+        CHECK(max_bits > 0);
+    } END_FOR_ALL_REGISTERS;
+
+    return true;
+}
+END_TEST(testJitRegisterSet_FPU_Aliases)
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -158,16 +158,21 @@ TypedArrayObject::trace(JSTracer* trc, J
 }
 
 void
 TypedArrayObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(!IsInsideNursery(obj));
     TypedArrayObject* curObj = &obj->as<TypedArrayObject>();
 
+    // Template objects or discarded objects (which didn't have enough room
+    // for inner elements). Don't have anything to free.
+    if (!curObj->elementsRaw())
+        return;
+
     curObj->assertZeroLengthArrayData();
 
     // Typed arrays with a buffer object do not need to be free'd
     if (curObj->hasBuffer())
         return;
 
     // Free the data slot pointer if it does not point into the old JSObject.
     if (!curObj->hasInlineElements())
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -201,16 +201,28 @@ static const Register FuncPtrCallTemp = 
 // worth it yet.  CallTempReg2 seems safe.
 static const Register ScratchRegARM = CallTempReg2;
 
 # define INT_DIV_I64_CALLOUT
 # define I64_TO_FLOAT_CALLOUT
 # define FLOAT_TO_I64_CALLOUT
 #endif
 
+template<MIRType t>
+struct RegTypeOf {
+    static_assert(t == MIRType::Float32 || t == MIRType::Double, "Float mask type");
+};
+
+template<> struct RegTypeOf<MIRType::Float32> {
+    static constexpr RegTypeName value = RegTypeName::Float32;
+};
+template<> struct RegTypeOf<MIRType::Double> {
+    static constexpr RegTypeName value = RegTypeName::Float64;
+};
+
 class BaseCompiler
 {
     // We define our own ScratchRegister abstractions, deferring to
     // the platform's when possible.
 
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
     typedef ScratchDoubleScope ScratchF64;
 #else
@@ -786,45 +798,32 @@ class BaseCompiler
     // ScratchFloat32Register is the same as ScratchDoubleRegister.
     //
     // It's a basic invariant of the AllocatableRegisterSet that it
     // deals properly with aliasing of registers: if s0 or s1 are
     // allocated then d0 is not allocatable; if s0 and s1 are freed
     // individually then d0 becomes allocatable.
 
     template<MIRType t>
-    FloatRegisters::SetType maskFromTypeFPU() {
-        static_assert(t == MIRType::Float32 || t == MIRType::Double, "Float mask type");
-        if (t == MIRType::Float32)
-            return FloatRegisters::AllSingleMask;
-        return FloatRegisters::AllDoubleMask;
-    }
-
-    template<MIRType t>
     bool hasFPU() {
-        return !!(availFPU_.bits() & maskFromTypeFPU<t>());
+        return availFPU_.hasAny<RegTypeOf<t>::value>();
     }
 
     bool isAvailable(FloatRegister r) {
         return availFPU_.has(r);
     }
 
     void allocFPU(FloatRegister r) {
         MOZ_ASSERT(isAvailable(r));
         availFPU_.take(r);
     }
 
     template<MIRType t>
     FloatRegister allocFPU() {
-        MOZ_ASSERT(hasFPU<t>());
-        FloatRegister r =
-            FloatRegisterSet::Intersect(FloatRegisterSet(availFPU_.bits()),
-                                        FloatRegisterSet(maskFromTypeFPU<t>())).getAny();
-        availFPU_.take(r);
-        return r;
+        return availFPU_.takeAny<RegTypeOf<t>::value>();
     }
 
     void freeFPU(FloatRegister r) {
         availFPU_.add(r);
     }
 
     ////////////////////////////////////////////////////////////
     //
--- a/toolkit/components/telemetry/ScalarInfo.h
+++ b/toolkit/components/telemetry/ScalarInfo.h
@@ -1,27 +1,40 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* 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/. */
 
 #ifndef TelemetryScalarInfo_h__
 #define TelemetryScalarInfo_h__
 
+#include "nsXULAppAPI.h"
+
 // This module is internal to Telemetry. It defines a structure that holds the
 // scalar info. It should only be used by TelemetryScalarData.h automatically
 // generated file and TelemetryScalar.cpp. This should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace {
+
+enum class RecordedProcessType : uint32_t {
+  Main       = (1 << GeckoProcessType_Default),  // Also known as "parent process"
+  Content    = (1 << GeckoProcessType_Content),
+  Gpu        = (1 << GeckoProcessType_GPU),
+  AllChilds  = 0xFFFFFFFF - 1,  // All the children processes (i.e. content, gpu, ...)
+  All        = 0xFFFFFFFF       // All the processes
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RecordedProcessType)
+
 struct ScalarInfo {
   uint32_t kind;
   uint32_t name_offset;
   uint32_t expiration_offset;
   uint32_t dataset;
+  RecordedProcessType record_in_processes;
   bool keyed;
 
   const char *name() const;
   const char *expiration() const;
 };
 } // namespace
 
 #endif // TelemetryScalarInfo_h__
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -279,8 +279,44 @@ telemetry.test:
     bug_numbers:
       - 1277806
     description: A testing keyed boolean scalar; not meant to be touched.
     expires: never
     kind: boolean
     keyed: true
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+  content_only_uint:
+    bug_numbers:
+      - 1278556
+    description: A testing uint scalar; not meant to be touched.
+    expires: never
+    kind: uint
+    notification_emails:
+      - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'content'
+
+  all_processes_uint:
+    bug_numbers:
+      - 1278556
+    description: A testing uint scalar; not meant to be touched.
+    expires: never
+    kind: uint
+    notification_emails:
+      - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'all'
+
+  all_child_processes_string:
+    bug_numbers:
+      - 1278556
+    description: A testing string scalar; not meant to be touched.
+    expires: never
+    kind: string
+    notification_emails:
+      - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'all_childs'
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -38,16 +38,17 @@
 #include "nsITelemetry.h"
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "nsIMemoryReporter.h"
 #include "nsISeekableStream.h"
 #include "Telemetry.h"
 #include "TelemetryCommon.h"
 #include "TelemetryHistogram.h"
+#include "TelemetryIPCAccumulator.h"
 #include "TelemetryScalar.h"
 #include "TelemetryEvent.h"
 #include "WebrtcTelemetry.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsXULAppAPI.h"
@@ -2126,19 +2127,17 @@ TelemetryImpl::CreateTelemetryInstance()
       XRE_IsContentProcess() ||
       XRE_IsGPUProcess())
   {
     useTelemetry = true;
   }
 
   // First, initialize the TelemetryHistogram and TelemetryScalar global states.
   TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry);
-
-  // Only record scalars from the parent process.
-  TelemetryScalar::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
+  TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry);
 
   // Only record events from the parent process.
   TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
 
   // Now, create and initialize the Telemetry global state.
   sTelemetry = new TelemetryImpl();
 
   // AddRef for the local reference
@@ -2159,16 +2158,17 @@ TelemetryImpl::ShutdownTelemetry()
   ClearIOReporting();
   NS_IF_RELEASE(sTelemetry);
 
   // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global states,
   // so as to release any heap storage that would otherwise be kept alive by it.
   TelemetryHistogram::DeInitializeGlobalState();
   TelemetryScalar::DeInitializeGlobalState();
   TelemetryEvent::DeInitializeGlobalState();
+  TelemetryIPCAccumulator::DeInitializeGlobalState();
 }
 
 void
 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
                             SanitizedState state)
 {
   AutoHashtable<SlowSQLEntryType>* slowSQLMap = nullptr;
   if (state == Sanitized)
@@ -2640,17 +2640,17 @@ TelemetryImpl::SetEventRecordingEnabled(
 {
   TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::FlushBatchedChildTelemetry()
 {
-  TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+  TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
   return NS_OK;
 }
 
 size_t
 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   size_t n = aMallocSizeOf(this);
 
@@ -3100,16 +3100,30 @@ AccumulateChild(GeckoProcessType aProces
 
 void
 AccumulateChildKeyed(GeckoProcessType aProcessType,
                      const nsTArray<KeyedAccumulation>& aAccumulations)
 {
   TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations);
 }
 
+void
+UpdateChildScalars(GeckoProcessType aProcessType,
+                   const nsTArray<ScalarAction>& aScalarActions)
+{
+  TelemetryScalar::UpdateChildData(aProcessType, aScalarActions);
+}
+
+void
+UpdateChildKeyedScalars(GeckoProcessType aProcessType,
+                        const nsTArray<KeyedScalarAction>& aScalarActions)
+{
+  TelemetryScalar::UpdateChildKeyedData(aProcessType, aScalarActions);
+}
+
 const char*
 GetHistogramName(ID id)
 {
   return TelemetryHistogram::GetHistogramName(id);
 }
 
 bool
 CanRecordBase()
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -31,16 +31,18 @@
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
 struct Accumulation;
 struct KeyedAccumulation;
+struct ScalarAction;
+struct KeyedScalarAction;
 
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Create and destroy the underlying base::StatisticsRecorder singleton.
@@ -139,16 +141,30 @@ void AccumulateChild(GeckoProcessType aP
 /**
  * Accumulate child process data into keyed histograms for the given process type.
  *
  * @param aAccumulations - accumulation actions to perform
  */
 void AccumulateChildKeyed(GeckoProcessType aProcessType, const nsTArray<KeyedAccumulation>& aAccumulations);
 
 /**
+ * Update scalars for the given process type with the data coming from child process.
+ *
+ * @param aScalarActions - actions to update the scalar data
+ */
+void UpdateChildScalars(GeckoProcessType aProcessType, const nsTArray<ScalarAction>& aScalarActions);
+
+/**
+ * Update keyed  scalars for the given process type with the data coming from child process.
+ *
+ * @param aScalarActions - actions to update the keyed scalar data
+ */
+void UpdateChildKeyedScalars(GeckoProcessType aProcessType, const nsTArray<KeyedScalarAction>& aScalarActions);
+
+/**
  * Enable/disable recording for this histogram at runtime.
  * Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[].
  * id must be a valid telemetry enum, otherwise an assertion is triggered.
  *
  * @param id - histogram id
  * @param enabled - whether or not to enable recording from now on.
  */
 void SetHistogramRecordingEnabled(ID id, bool enabled);
--- a/toolkit/components/telemetry/TelemetryComms.h
+++ b/toolkit/components/telemetry/TelemetryComms.h
@@ -2,35 +2,64 @@
 /* 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
  */
 
 #ifndef Telemetry_Comms_h__
 #define Telemetry_Comms_h__
 
 #include "ipc/IPCMessageUtils.h"
+#include "nsITelemetry.h"
+#include "nsVariant.h"
 
 namespace mozilla {
 namespace Telemetry {
 
+// Histogram accumulation types.
 enum ID : uint32_t;
 
 struct Accumulation
 {
   mozilla::Telemetry::ID mId;
   uint32_t mSample;
 };
 
 struct KeyedAccumulation
 {
   mozilla::Telemetry::ID mId;
   uint32_t mSample;
   nsCString mKey;
 };
 
+// Scalar accumulation types.
+enum class ScalarID : uint32_t;
+
+enum class ScalarActionType : uint32_t {
+  eSet = 0,
+  eAdd = 1,
+  eSetMaximum = 2
+};
+
+struct ScalarAction
+{
+  ScalarID mId;
+  uint32_t mScalarType;
+  ScalarActionType mActionType;
+  nsCOMPtr<nsIVariant> mData;
+};
+
+struct KeyedScalarAction
+{
+  ScalarID mId;
+  uint32_t mScalarType;
+  ScalarActionType mActionType;
+  nsCString mKey;
+  nsCOMPtr<nsIVariant> mData;
+};
+
 } // namespace Telemetry
 } // namespace mozilla
 
 namespace IPC {
 
 template<>
 struct
 ParamTraits<mozilla::Telemetry::Accumulation>
@@ -74,11 +103,222 @@ ParamTraits<mozilla::Telemetry::KeyedAcc
         !ReadParam(aMsg, aIter, &(aResult->mKey))) {
       return false;
     }
 
     return true;
   }
 };
 
+/**
+ * IPC scalar data message serialization and de-serialization.
+ */
+template<>
+struct
+ParamTraits<mozilla::Telemetry::ScalarAction>
+{
+  typedef mozilla::Telemetry::ScalarAction paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    // Write the message type
+    aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mId));
+    WriteParam(aMsg, aParam.mScalarType);
+    WriteParam(aMsg, static_cast<uint32_t>(aParam.mActionType));
+
+    switch(aParam.mScalarType) {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t val = 0;
+          nsresult rv = aParam.mData->GetAsUint32(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Count Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          nsString val;
+          nsresult rv = aParam.mData->GetAsAString(val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Conversion failed.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool val = 0;
+          nsresult rv = aParam.mData->GetAsBool(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Boolean Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown scalar type.");
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    // Read the scalar ID and the scalar type.
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mScalarType)) ||
+        !ReadParam(aMsg, aIter, reinterpret_cast<uint32_t*>(&(aResult->mActionType)))) {
+      return false;
+    }
+
+    // De-serialize the data based on the scalar type.
+    nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+    switch (aResult->mScalarType)
+    {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t data = 0;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsUint32(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          nsString data;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsAString(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool data = false;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsBool(data))) {
+            return false;
+          }
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown scalar type.");
+        return false;
+    }
+
+    aResult->mData = outVar.forget();
+    return true;
+  }
+};
+
+/**
+ * IPC keyed scalar data message serialization and de-serialization.
+ */
+template<>
+struct
+ParamTraits<mozilla::Telemetry::KeyedScalarAction>
+{
+  typedef mozilla::Telemetry::KeyedScalarAction paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    // Write the message type
+    aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mId));
+    WriteParam(aMsg, aParam.mScalarType);
+    WriteParam(aMsg, static_cast<uint32_t>(aParam.mActionType));
+    WriteParam(aMsg, aParam.mKey);
+
+    switch(aParam.mScalarType) {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t val = 0;
+          nsresult rv = aParam.mData->GetAsUint32(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Keyed Count Scalar unable to convert variant to uint from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          // Keyed string scalars are not supported.
+          MOZ_ASSERT(false, "Keyed String Scalar unable to be write from child process. Not supported.");
+          break;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool val = 0;
+          nsresult rv = aParam.mData->GetAsBool(&val);
+          if (NS_FAILED(rv)) {
+            MOZ_ASSERT(false, "Keyed Boolean Scalar unable to convert variant to bool from child process.");
+            return;
+          }
+          WriteParam(aMsg, val);
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown keyed scalar type.");
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    // Read the scalar ID and the scalar type.
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mScalarType)) ||
+        !ReadParam(aMsg, aIter, reinterpret_cast<uint32_t*>(&(aResult->mActionType))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mKey))) {
+      return false;
+    }
+
+    // De-serialize the data based on the scalar type.
+    nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+    switch (aResult->mScalarType)
+    {
+      case nsITelemetry::SCALAR_COUNT:
+        {
+          uint32_t data = 0;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsUint32(data))) {
+            return false;
+          }
+          break;
+        }
+      case nsITelemetry::SCALAR_STRING:
+        {
+          // Keyed string scalars are not supported.
+          MOZ_ASSERT(false, "Keyed String Scalar unable to be read from child process. Not supported.");
+          return false;
+        }
+      case nsITelemetry::SCALAR_BOOLEAN:
+        {
+          bool data = false;
+          // De-serialize the data.
+          if (!ReadParam(aMsg, aIter, &data) ||
+              NS_FAILED(outVar->SetAsBool(data))) {
+            return false;
+          }
+          break;
+        }
+      default:
+        MOZ_ASSERT(false, "Unknown keyed scalar type.");
+        return false;
+    }
+
+    aResult->mData = outVar.forget();
+    return true;
+  }
+};
+
 } // namespace IPC
 
 #endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -9,40 +9,37 @@
 #include "js/GCAPI.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsITelemetry.h"
 
-#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ToJSValue.h"
-#include "mozilla/gfx/GPUParent.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/StaticMutex.h"
-#include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryHistogram.h"
+#include "TelemetryIPCAccumulator.h"
 
 #include "base/histogram.h"
 
 using base::Histogram;
 using base::StatisticsRecorder;
 using base::BooleanHistogram;
 using base::CountHistogram;
 using base::FlagHistogram;
 using base::LinearHistogram;
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
-using mozilla::StaticAutoPtr;
 using mozilla::Telemetry::Accumulation;
 using mozilla::Telemetry::KeyedAccumulation;
 using mozilla::Telemetry::Common::LogToBrowserConsole;
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
@@ -192,23 +189,16 @@ bool gCorruptHistograms[mozilla::Telemet
 // This is for gHistograms, gHistogramStringTable
 #include "TelemetryHistogramData.inc"
 
 AddonMapType gAddonMap;
 
 // The singleton StatisticsRecorder object for this process.
 base::StatisticsRecorder* gStatisticsRecorder = nullptr;
 
-// For batching and sending child process accumulations to the parent
-nsITimer* gIPCTimer = nullptr;
-mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false);
-mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false);
-StaticAutoPtr<nsTArray<Accumulation>> gAccumulations;
-StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedAccumulations;
-
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE CONSTANTS
 
@@ -218,27 +208,16 @@ namespace {
 const mozilla::Telemetry::ID kRecordingInitiallyDisabledIDs[] = {
   mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
 
   // The array must not be empty. Leave these item here.
   mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
   mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
 };
 
-// Sending each remote accumulation immediately places undue strain on the
-// IPC subsystem. Batch the remote accumulations for a period of time before
-// sending them all at once. This value was chosen as a balance between data
-// timeliness and performance (see bug 1218576)
-const uint32_t kBatchTimeoutMs = 2000;
-
-// To stop growing unbounded in memory while waiting for kBatchTimeoutMs to
-// drain the g*Accumulations arrays, request an immediate flush if the arrays
-// manage to reach this high water mark of elements.
-const size_t kAccumulationsArrayHighWaterMark = 5 * 1024;
-
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Misc small helpers
 
@@ -335,28 +314,16 @@ HistogramInfo::label_id(const char* labe
       *labelId = i;
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
-void internal_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent)
-{
-  nsCOMPtr<nsIRunnable> event(aEvent);
-  nsCOMPtr<nsIThread> thread;
-  nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("NS_FAILED DispatchToMainThread. Maybe we're shutting down?");
-    return;
-  }
-  thread->Dispatch(event, 0);
-}
-
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram Get, Add, Clone, Clear functions
 
@@ -1323,71 +1290,28 @@ internal_SetHistogramRecordingEnabled(mo
       h->SetRecordingEnabled(aEnabled);
       return;
     }
   }
 
   MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
 }
 
-void internal_armIPCTimerMainThread()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  gIPCTimerArming = false;
-  if (gIPCTimerArmed) {
-    return;
-  }
-  if (!gIPCTimer) {
-    CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer);
-  }
-  if (gIPCTimer) {
-    gIPCTimer->InitWithFuncCallback(TelemetryHistogram::IPCTimerFired,
-                                    nullptr, kBatchTimeoutMs,
-                                    nsITimer::TYPE_ONE_SHOT);
-    gIPCTimerArmed = true;
-  }
-}
-
-void internal_armIPCTimer()
-{
-  if (gIPCTimerArmed || gIPCTimerArming) {
-    return;
-  }
-  gIPCTimerArming = true;
-  if (NS_IsMainThread()) {
-    internal_armIPCTimerMainThread();
-  } else {
-    internal_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
-      StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-      internal_armIPCTimerMainThread();
-    }));
-  }
-}
-
 bool
 internal_RemoteAccumulate(mozilla::Telemetry::ID aId, uint32_t aSample)
 {
   if (XRE_IsParentProcess()) {
     return false;
   }
   Histogram *h;
   nsresult rv = internal_GetHistogramByEnumId(aId, &h, GeckoProcessType_Default);
   if (NS_SUCCEEDED(rv) && !h->IsRecordingEnabled()) {
     return true;
   }
-  if (!gAccumulations) {
-    gAccumulations = new nsTArray<Accumulation>();
-  }
-  if (gAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
-    internal_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
-      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
-    }));
-  }
-  gAccumulations->AppendElement(Accumulation{aId, aSample});
-  internal_armIPCTimer();
+  TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample);
   return true;
 }
 
 bool
 internal_RemoteAccumulate(mozilla::Telemetry::ID aId,
                     const nsCString& aKey, uint32_t aSample)
 {
   if (XRE_IsParentProcess()) {
@@ -1395,26 +1319,17 @@ internal_RemoteAccumulate(mozilla::Telem
   }
   const HistogramInfo& th = gHistograms[aId];
   KeyedHistogram* keyed
      = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
   MOZ_ASSERT(keyed);
   if (!keyed->IsRecordingEnabled()) {
     return false;
   }
-  if (!gKeyedAccumulations) {
-    gKeyedAccumulations = new nsTArray<KeyedAccumulation>();
-  }
-  if (gKeyedAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
-    internal_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
-      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
-    }));
-  }
-  gKeyedAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
-  internal_armIPCTimer();
+  TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample);
   return true;
 }
 
 void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
 {
   bool isValid = internal_IsHistogramEnumId(aHistogram);
   MOZ_ASSERT(isValid, "Accumulation using invalid id");
   if (!internal_CanRecordBase() || !isValid ||
@@ -2122,21 +2037,16 @@ void TelemetryHistogram::InitializeGloba
 void TelemetryHistogram::DeInitializeGlobalState()
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   gCanRecordBase = false;
   gCanRecordExtended = false;
   gHistogramMap.Clear();
   gKeyedHistograms.Clear();
   gAddonMap.Clear();
-  gAccumulations = nullptr;
-  gKeyedAccumulations = nullptr;
-  if (gIPCTimer) {
-    NS_RELEASE(gIPCTimer);
-  }
   gInitDone = false;
 }
 
 #ifdef DEBUG
 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   return gInitDone;
 }
@@ -2647,67 +2557,8 @@ TelemetryHistogram::GetHistogramSizesofI
   StatisticsRecorder::Histograms hs;
   StatisticsRecorder::GetHistograms(&hs);
   size_t n = 0;
   for (auto h : hs) {
     n += h->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
-
-// This method takes the lock only to double-buffer the batched telemetry.
-// It releases the lock before calling out to IPC code which can (and does)
-// Accumulate (which would deadlock)
-//
-// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
-// unset gIPCTimerArmed until the IPC completes
-//
-// This function must be called on the main thread, otherwise IPC will fail.
-void
-TelemetryHistogram::IPCTimerFired(nsITimer* aTimer, void* aClosure)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  nsTArray<Accumulation> accumulationsToSend;
-  nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
-  {
-    StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-    if (gAccumulations) {
-      accumulationsToSend.SwapElements(*gAccumulations);
-    }
-    if (gKeyedAccumulations) {
-      keyedAccumulationsToSend.SwapElements(*gKeyedAccumulations);
-    }
-  }
-
-  switch (XRE_GetProcessType()) {
-    case GeckoProcessType_Content: {
-      mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
-      mozilla::Unused << NS_WARN_IF(!contentChild);
-      if (contentChild) {
-        if (accumulationsToSend.Length()) {
-          mozilla::Unused <<
-            NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
-        }
-        if (keyedAccumulationsToSend.Length()) {
-          mozilla::Unused <<
-            NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
-        }
-      }
-      break;
-    }
-    case GeckoProcessType_GPU: {
-      if (mozilla::gfx::GPUParent* gpu = mozilla::gfx::GPUParent::GetSingleton()) {
-        if (accumulationsToSend.Length()) {
-          mozilla::Unused << gpu->SendAccumulateChildHistogram(accumulationsToSend);
-        }
-        if (keyedAccumulationsToSend.Length()) {
-          mozilla::Unused << gpu->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend);
-        }
-      }
-      break;
-    }
-    default:
-      MOZ_ASSERT_UNREACHABLE("Unsupported process type");
-      break;
-  }
-
-  gIPCTimerArmed = false;
-}
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -92,13 +92,11 @@ nsresult
 GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret);
 
 size_t
 GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 size_t
 GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
-void
-IPCTimerFired(nsITimer* aTimer, void* aClosure);
 } // namespace TelemetryHistogram
 
 #endif // TelemetryHistogram_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TelemetryIPCAccumulator.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "TelemetryHistogram.h"
+#include "TelemetryScalar.h"
+
+using mozilla::StaticMutex;
+using mozilla::StaticMutexAutoLock;
+using mozilla::StaticAutoPtr;
+using mozilla::Telemetry::Accumulation;
+using mozilla::Telemetry::KeyedAccumulation;
+using mozilla::Telemetry::ScalarActionType;
+using mozilla::Telemetry::ScalarAction;
+using mozilla::Telemetry::KeyedScalarAction;
+
+// Sending each remote accumulation immediately places undue strain on the
+// IPC subsystem. Batch the remote accumulations for a period of time before
+// sending them all at once. This value was chosen as a balance between data
+// timeliness and performance (see bug 1218576)
+const uint32_t kBatchTimeoutMs = 2000;
+
+// To stop growing unbounded in memory while waiting for kBatchTimeoutMs to
+// drain the g*Accumulations arrays, request an immediate flush if the arrays
+// manage to reach this high water mark of elements.
+const size_t kHistogramAccumulationsArrayHighWaterMark = 5 * 1024;
+
+// For batching and sending child process accumulations to the parent
+nsITimer* gIPCTimer = nullptr;
+mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false);
+mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false);
+
+// For batching and sending child process accumulations to the parent
+StaticAutoPtr<nsTArray<Accumulation>> gHistogramAccumulations;
+StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedHistogramAccumulations;
+StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions;
+StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions;
+
+// This is a StaticMutex rather than a plain Mutex so that (1)
+// it gets initialised in a thread-safe manner the first time
+// it is used, and (2) because it is never de-initialised, and
+// a normal Mutex would show up as a leak in BloatView.  StaticMutex
+// also has the "OffTheBooks" property, so it won't show as a leak
+// in BloatView.
+static StaticMutex gTelemetryIPCAccumulatorMutex;
+
+namespace {
+
+void DoArmIPCTimerMainThread(const StaticMutexAutoLock& lock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  gIPCTimerArming = false;
+  if (gIPCTimerArmed) {
+    return;
+  }
+  if (!gIPCTimer) {
+    CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer);
+  }
+  if (gIPCTimer) {
+    gIPCTimer->InitWithFuncCallback(TelemetryIPCAccumulator::IPCTimerFired,
+                                    nullptr, kBatchTimeoutMs,
+                                    nsITimer::TYPE_ONE_SHOT);
+    gIPCTimerArmed = true;
+  }
+}
+
+void ArmIPCTimer(const StaticMutexAutoLock& lock)
+{
+  if (gIPCTimerArmed || gIPCTimerArming) {
+    return;
+  }
+  gIPCTimerArming = true;
+  if (NS_IsMainThread()) {
+    DoArmIPCTimerMainThread(lock);
+  } else {
+    TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+      DoArmIPCTimerMainThread(locker);
+    }));
+  }
+}
+
+} // anonymous namespace
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryIPCAccumulator::
+
+void
+TelemetryIPCAccumulator::AccumulateChildHistogram(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  if (!gHistogramAccumulations) {
+    gHistogramAccumulations = new nsTArray<Accumulation>();
+  }
+  if (gHistogramAccumulations->Length() == kHistogramAccumulationsArrayHighWaterMark) {
+    TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gHistogramAccumulations->AppendElement(Accumulation{aId, aSample});
+  ArmIPCTimer(locker);
+}
+
+void
+TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(mozilla::Telemetry::ID aId,
+                                                       const nsCString& aKey, uint32_t aSample)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  if (!gKeyedHistogramAccumulations) {
+    gKeyedHistogramAccumulations = new nsTArray<KeyedAccumulation>();
+  }
+  if (gKeyedHistogramAccumulations->Length() == kHistogramAccumulationsArrayHighWaterMark) {
+    TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gKeyedHistogramAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
+  ArmIPCTimer(locker);
+}
+
+void
+TelemetryIPCAccumulator::RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind,
+                                                 ScalarActionType aAction, nsIVariant* aValue)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  // Make sure to have the storage.
+  if (!gChildScalarsActions) {
+    gChildScalarsActions = new nsTArray<ScalarAction>();
+  }
+  // Store the action.
+  gChildScalarsActions->AppendElement(ScalarAction{aId, aKind, aAction, aValue});
+  ArmIPCTimer(locker);
+}
+
+void
+TelemetryIPCAccumulator::RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId,
+                                                      const nsAString& aKey, uint32_t aKind,
+                                                      ScalarActionType aAction, nsIVariant* aValue)
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  // Make sure to have the storage.
+  if (!gChildKeyedScalarsActions) {
+    gChildKeyedScalarsActions = new nsTArray<KeyedScalarAction>();
+  }
+  // Store the action.
+  gChildKeyedScalarsActions->AppendElement(
+    KeyedScalarAction{aId, aKind, aAction, NS_ConvertUTF16toUTF8(aKey), aValue});
+  ArmIPCTimer(locker);
+}
+
+// This method takes the lock only to double-buffer the batched telemetry.
+// It releases the lock before calling out to IPC code which can (and does)
+// Accumulate (which would deadlock)
+//
+// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't
+// unset gIPCTimerArmed until the IPC completes
+//
+// This function must be called on the main thread, otherwise IPC will fail.
+void
+TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Get the accumulated data and free the storage buffer.
+  nsTArray<Accumulation> accumulationsToSend;
+  nsTArray<KeyedAccumulation> keyedAccumulationsToSend;
+  nsTArray<ScalarAction> scalarsToSend;
+  nsTArray<KeyedScalarAction> keyedScalarsToSend;
+  {
+    StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+    if (gHistogramAccumulations) {
+      accumulationsToSend.SwapElements(*gHistogramAccumulations);
+    }
+    if (gKeyedHistogramAccumulations) {
+      keyedAccumulationsToSend.SwapElements(*gKeyedHistogramAccumulations);
+    }
+    // Copy the scalar actions.
+    if (gChildScalarsActions) {
+      scalarsToSend.SwapElements(*gChildScalarsActions);
+    }
+    if (gChildKeyedScalarsActions) {
+      keyedScalarsToSend.SwapElements(*gChildKeyedScalarsActions);
+    }
+  }
+
+  // Send the accumulated data to the parent process.
+  switch (XRE_GetProcessType()) {
+    case GeckoProcessType_Content: {
+      mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
+      mozilla::Unused << NS_WARN_IF(!contentChild);
+      if (contentChild) {
+        if (accumulationsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
+        }
+        if (keyedAccumulationsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
+        }
+        if (scalarsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendUpdateChildScalars(scalarsToSend));
+        }
+        if (keyedScalarsToSend.Length()) {
+          mozilla::Unused <<
+            NS_WARN_IF(!contentChild->SendUpdateChildKeyedScalars(keyedScalarsToSend));
+        }
+      }
+      break;
+    }
+    case GeckoProcessType_GPU: {
+      if (mozilla::gfx::GPUParent* gpu = mozilla::gfx::GPUParent::GetSingleton()) {
+        if (accumulationsToSend.Length()) {
+          mozilla::Unused << gpu->SendAccumulateChildHistogram(accumulationsToSend);
+        }
+        if (keyedAccumulationsToSend.Length()) {
+          mozilla::Unused << gpu->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend);
+        }
+        if (scalarsToSend.Length()) {
+          mozilla::Unused << gpu->SendUpdateChildScalars(scalarsToSend);
+        }
+        if (keyedScalarsToSend.Length()) {
+          mozilla::Unused << gpu->SendUpdateChildKeyedScalars(keyedScalarsToSend);
+        }
+      }
+      break;
+    }
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unsupported process type");
+      break;
+  }
+
+  gIPCTimerArmed = false;
+}
+
+void
+TelemetryIPCAccumulator::DeInitializeGlobalState()
+{
+  StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex);
+  if (gIPCTimer) {
+    NS_RELEASE(gIPCTimer);
+  }
+
+  gHistogramAccumulations = nullptr;
+  gKeyedHistogramAccumulations = nullptr;
+  gChildScalarsActions = nullptr;
+  gChildKeyedScalarsActions = nullptr;
+}
+
+void
+TelemetryIPCAccumulator::DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent)
+{
+  nsCOMPtr<nsIRunnable> event(aEvent);
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("NS_FAILED DispatchToMainThread. Maybe we're shutting down?");
+    return;
+  }
+  thread->Dispatch(event, 0);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryIPCAccumulator.h
@@ -0,0 +1,49 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+#ifndef TelemetryIPCAccumulator_h__
+#define TelemetryIPCAccumulator_h__
+
+#include "mozilla/AlreadyAddRefed.h"
+
+class nsIRunnable;
+class nsITimer;
+class nsAString;
+class nsCString;
+class nsIVariant;
+
+namespace mozilla {
+namespace Telemetry {
+
+enum ID : uint32_t;
+enum class ScalarID : uint32_t;
+enum class ScalarActionType : uint32_t;
+
+} // Telemetry
+} // mozilla
+
+namespace TelemetryIPCAccumulator {
+
+// Histogram accumulation functions.
+void AccumulateChildHistogram(mozilla::Telemetry::ID aId, uint32_t aSample);
+void AccumulateChildKeyedHistogram(mozilla::Telemetry::ID aId, const nsCString& aKey,
+                                   uint32_t aSample);
+
+// Scalar accumulation functions.
+void RecordChildScalarAction(mozilla::Telemetry::ScalarID aId, uint32_t aKind,
+                             mozilla::Telemetry::ScalarActionType aAction, nsIVariant* aValue);
+
+void RecordChildKeyedScalarAction(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
+                                  uint32_t aKind, mozilla::Telemetry::ScalarActionType aAction,
+                                  nsIVariant* aValue);
+
+void IPCTimerFired(nsITimer* aTimer, void* aClosure);
+void DeInitializeGlobalState();
+
+void DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent);
+
+}
+
+#endif // TelemetryIPCAccumulator_h__
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -5,33 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsITelemetry.h"
 #include "nsIVariant.h"
 #include "nsVariant.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsIXPConnect.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/Unused.h"
 
+#include "TelemetryComms.h"
 #include "TelemetryCommon.h"
+#include "TelemetryIPCAccumulator.h"
 #include "TelemetryScalar.h"
 #include "TelemetryScalarData.h"
 
 using mozilla::StaticMutex;
 using mozilla::StaticMutexAutoLock;
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::IsInDataset;
 using mozilla::Telemetry::Common::LogToBrowserConsole;
+using mozilla::Telemetry::ScalarActionType;
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
 // * Functions named internal_*: these can only be reached via an
 //   interface function (TelemetryScalar::*). They expect the interface
@@ -74,16 +78,17 @@ const uint32_t kMaximumKeyStringLength =
 const uint32_t kMaximumStringValueLength = 50;
 const uint32_t kScalarCount =
   static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount);
 
 enum class ScalarResult : uint8_t {
   // Nothing went wrong.
   Ok,
   // General Scalar Errors
+  CannotRecordInProcess,
   OperationNotSupported,
   InvalidType,
   InvalidValue,
   // Keyed Scalar Errors
   KeyTooLong,
   TooManyKeys,
   // String Scalar Errors
   StringTooLong,
@@ -102,16 +107,17 @@ typedef AutoHashtable<CharPtrEntryType> 
  * @param aSr The error code used internally in this module.
  * @return {nsresult} A NS_* error code.
  */
 nsresult
 MapToNsResult(ScalarResult aSr)
 {
   switch (aSr) {
     case ScalarResult::Ok:
+    case ScalarResult::CannotRecordInProcess:
       return NS_OK;
     case ScalarResult::OperationNotSupported:
       return NS_ERROR_NOT_AVAILABLE;
     case ScalarResult::StringTooLong:
       // We don't want to throw if we're setting a string that is too long.
       return NS_OK;
     case ScalarResult::InvalidType:
     case ScalarResult::InvalidValue:
@@ -129,16 +135,56 @@ MapToNsResult(ScalarResult aSr)
 }
 
 bool
 IsValidEnumId(mozilla::Telemetry::ScalarID aID)
 {
   return aID < mozilla::Telemetry::ScalarID::ScalarCount;
 }
 
+/**
+ * The following helpers are used to get a nsIVariant from a uint32_t,
+ * nsAString or bool data.
+ */
+nsresult
+GetVariant(uint32_t aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsUint32(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
+nsresult
+GetVariant(const nsAString& aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsAString(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
+nsresult
+GetVariant(bool aValue, nsCOMPtr<nsIVariant>& aResult)
+{
+  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+  nsresult rv = outVar->SetAsBool(aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aResult = outVar.forget();
+  return NS_OK;
+}
+
 // Implements the methods for ScalarInfo.
 const char *
 ScalarInfo::name() const
 {
   return &gScalarsStringTable[this->name_offset];
 }
 
 const char *
@@ -283,23 +329,17 @@ ScalarUnsigned::SetMaximum(uint32_t aVal
   if (aValue > mStorage) {
     mStorage = aValue;
   }
 }
 
 nsresult
 ScalarUnsigned::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsUint32(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarUnsigned::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 }
 
@@ -384,23 +424,17 @@ ScalarString::SetValue(const nsAString& 
     return ScalarResult::StringTooLong;
   }
   return ScalarResult::Ok;
 }
 
 nsresult
 ScalarString::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsAString(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarString::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
   n+= mStorage.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
   return n;
@@ -458,23 +492,17 @@ void
 ScalarBoolean::SetValue(bool aValue)
 {
   mStorage = aValue;
 }
 
 nsresult
 ScalarBoolean::GetValue(nsCOMPtr<nsIVariant>& aResult) const
 {
-  nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
-  nsresult rv = outVar->SetAsBool(mStorage);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  aResult = outVar.forget();
-  return NS_OK;
+  return GetVariant(mStorage, aResult);
 }
 
 size_t
 ScalarBoolean::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this);
 }
 
@@ -701,18 +729,21 @@ KeyedScalar::SizeOfIncludingThis(mozilla
   for (auto iter = mScalarKeys.Iter(); !iter.Done(); iter.Next()) {
     ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
     n += scalar->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
 
 typedef nsUint32HashKey ScalarIDHashKey;
+typedef nsUint32HashKey ProcessIDHashKey;
 typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType;
 typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> KeyedScalarStorageMapType;
+typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> ProcessesScalarsMapType;
+typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> ProcessesKeyedScalarsMapType;
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE STATE, SHARED BY ALL THREADS
 
@@ -721,23 +752,23 @@ namespace {
 // Set to true once this global state has been initialized.
 bool gInitDone = false;
 
 bool gCanRecordBase;
 bool gCanRecordExtended;
 
 // The Name -> ID cache map.
 ScalarMapType gScalarNameIDMap(kScalarCount);
-// The ID -> Scalar Object map. This is a nsClassHashtable, it owns
-// the scalar instance and takes care of deallocating them when they
-// get removed from the map.
-ScalarStorageMapType gScalarStorageMap;
-// The ID -> Keyed Scalar Object map. As for plain scalars, this is
-// nsClassHashtable. See above.
-KeyedScalarStorageMapType gKeyedScalarStorageMap;
+
+// The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a nsClassHashtable,
+// it owns the scalar instances and takes care of deallocating them when they are
+// removed from the map.
+ProcessesScalarsMapType gScalarStorageMap;
+// As above, for the keyed scalars.
+ProcessesKeyedScalarsMapType gKeyedScalarStorageMap;
 
 } // namespace
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Function that may call JS code.
 
@@ -756,16 +787,17 @@ namespace {
  *
  * @param aSr The error code.
  * @return true if the error should be logged, false otherwise.
  */
 bool
 internal_ShouldLogError(ScalarResult aSr)
 {
   switch (aSr) {
+    case ScalarResult::CannotRecordInProcess: MOZ_FALLTHROUGH;
     case ScalarResult::StringTooLong: MOZ_FALLTHROUGH;
     case ScalarResult::KeyTooLong: MOZ_FALLTHROUGH;
     case ScalarResult::TooManyKeys: MOZ_FALLTHROUGH;
     case ScalarResult::UnsignedNegativeValue: MOZ_FALLTHROUGH;
     case ScalarResult::UnsignedTruncatedValue:
       // Intentional fall-through.
       return true;
 
@@ -786,16 +818,19 @@ internal_ShouldLogError(ScalarResult aSr
  */
 void
 internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
 {
   nsAutoString errorMessage;
   AppendUTF8toUTF16(aScalarName, errorMessage);
 
   switch (aSr) {
+    case ScalarResult::CannotRecordInProcess:
+      errorMessage.Append(NS_LITERAL_STRING(" - Cannot record the scalar in the current process."));
+      break;
     case ScalarResult::StringTooLong:
       errorMessage.Append(NS_LITERAL_STRING(" - Truncating scalar value to 50 characters."));
       break;
     case ScalarResult::KeyTooLong:
       errorMessage.Append(NS_LITERAL_STRING(" - The key length must be limited to 70 characters."));
       break;
     case ScalarResult::TooManyKeys:
       errorMessage.Append(NS_LITERAL_STRING(" - Keyed scalars cannot have more than 100 keys."));
@@ -848,16 +883,39 @@ internal_InfoForScalarID(mozilla::Teleme
  * @return true if aId refers to a keyed scalar, false otherwise.
  */
 bool
 internal_IsKeyedScalar(mozilla::Telemetry::ScalarID aId)
 {
   return internal_InfoForScalarID(aId).keyed;
 }
 
+/**
+ * Check if we're allowed to record the given scalar in the current
+ * process.
+ *
+ * @param aId The id of the scalar to check.
+ * @return true if the scalar is allowed to be recorded in the current process, false
+ *         otherwise.
+ */
+bool
+internal_CanRecordProcess(mozilla::Telemetry::ScalarID aId)
+{
+  // Get the scalar info from the id.
+  const ScalarInfo &info = internal_InfoForScalarID(aId);
+
+  bool recordAllChild = !!(info.record_in_processes & RecordedProcessType::AllChilds);
+  // We can use (1 << ProcessType) due to the way RecordedProcessType is defined
+  // in ScalarInfo.h
+  bool canRecordProcess =
+    !!(info.record_in_processes & static_cast<RecordedProcessType>(1 << XRE_GetProcessType()));
+
+  return canRecordProcess || (!XRE_IsParentProcess() && recordAllChild);
+}
+
 bool
 internal_CanRecordForScalarID(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar info from the id.
   const ScalarInfo &info = internal_InfoForScalarID(aId);
 
   // Can we record at all?
   bool canRecordBase = internal_CanRecordBase();
@@ -900,79 +958,96 @@ internal_GetEnumByScalarName(const nsACS
   return NS_OK;
 }
 
 /**
  * Get a scalar object by its enum id. This implicitly allocates the scalar
  * object in the storage if it wasn't previously allocated.
  *
  * @param aId The scalar id.
+ * @param aProcessStorage This drives the selection of the map to use to store
+ *        the scalar data coming from child processes. This is only meaningful when
+ *        this function is called in parent process. If that's the case, if
+ *        this is not |GeckoProcessType_Default|, the process id is used to
+ *        allocate and store the scalars.
  * @param aRes The output variable that stores scalar object.
  * @return
  *   NS_ERROR_INVALID_ARG if the scalar id is unknown.
  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
  *   NS_OK if the scalar was found. If that's the case, aResult contains a
  *   valid pointer to a scalar type.
  */
 nsresult
-internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, ScalarBase** aRet)
+internal_GetScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage,
+                         ScalarBase** aRet)
 {
   if (!IsValidEnumId(aId)) {
     MOZ_ASSERT(false, "Requested a scalar with an invalid id.");
     return NS_ERROR_INVALID_ARG;
   }
 
   const uint32_t id = static_cast<uint32_t>(aId);
+  const ScalarInfo &info = gScalars[id];
 
   ScalarBase* scalar = nullptr;
-  if (gScalarStorageMap.Get(id, &scalar)) {
+  ScalarStorageMapType* scalarStorage = nullptr;
+  // Initialize the scalar storage to the parent storage. This will get
+  // set to the child storage if needed.
+  uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
+
+  // Get the process-specific storage or create one if it's not
+  // available.
+  if (!gScalarStorageMap.Get(storageId, &scalarStorage)) {
+    scalarStorage = new ScalarStorageMapType();
+    gScalarStorageMap.Put(storageId, scalarStorage);
+  }
+
+  // Check if the scalar is already allocated in the parent or in the child storage.
+  if (scalarStorage->Get(id, &scalar)) {
     *aRet = scalar;
     return NS_OK;
   }
 
-  const ScalarInfo &info = gScalars[id];
-
   if (IsExpiredVersion(info.expiration())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   scalar = internal_ScalarAllocate(info.kind);
   if (!scalar) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  gScalarStorageMap.Put(id, scalar);
-
+  scalarStorage->Put(id, scalar);
   *aRet = scalar;
   return NS_OK;
 }
 
 /**
  * Get a scalar object by its enum id, if we're allowed to record it.
  *
  * @param aId The scalar id.
  * @return The ScalarBase instance or nullptr if we're not allowed to record
  *         the scalar.
  */
 ScalarBase*
 internal_GetRecordableScalar(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar by the enum (it also internally checks for aId validity).
   ScalarBase* scalar = nullptr;
-  nsresult rv = internal_GetScalarByEnum(aId, &scalar);
+  nsresult rv = internal_GetScalarByEnum(aId, GeckoProcessType_Default, &scalar);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   if (internal_IsKeyedScalar(aId)) {
     return nullptr;
   }
 
   // Are we allowed to record this scalar?
-  if (!internal_CanRecordForScalarID(aId)) {
+  if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) {
     return nullptr;
   }
 
   return scalar;
 }
 
 } // namespace
 
@@ -985,86 +1060,102 @@ internal_GetRecordableScalar(mozilla::Te
 
 namespace {
 
 /**
  * Get a keyed scalar object by its enum id. This implicitly allocates the keyed
  * scalar object in the storage if it wasn't previously allocated.
  *
  * @param aId The scalar id.
- * @param aRes The output variable that stores scalar object.
+ * @param aProcessStorage This drives the selection of the map to use to store
+ *        the scalar data coming from child processes. This is only meaningful when
+ *        this function is called in parent process. If that's the case, if
+ *        this is not |GeckoProcessType_Default|, the process id is used to
+ *        allocate and store the scalars.
+ * @param aRet The output variable that stores scalar object.
  * @return
  *   NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed string
  *                        scalar.
  *   NS_ERROR_NOT_AVAILABLE if the scalar is expired.
  *   NS_OK if the scalar was found. If that's the case, aResult contains a
  *   valid pointer to a scalar type.
  */
 nsresult
-internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, KeyedScalar** aRet)
+internal_GetKeyedScalarByEnum(mozilla::Telemetry::ScalarID aId, GeckoProcessType aProcessStorage,
+                              KeyedScalar** aRet)
 {
   if (!IsValidEnumId(aId)) {
     MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id.");
     return NS_ERROR_INVALID_ARG;
   }
 
   const uint32_t id = static_cast<uint32_t>(aId);
+  const ScalarInfo &info = gScalars[id];
 
   KeyedScalar* scalar = nullptr;
-  if (gKeyedScalarStorageMap.Get(id, &scalar)) {
+  KeyedScalarStorageMapType* scalarStorage = nullptr;
+  // Initialize the scalar storage to the parent storage. This will get
+  // set to the child storage if needed.
+  uint32_t storageId = static_cast<uint32_t>(aProcessStorage);
+
+  // Get the process-specific storage or create one if it's not
+  // available.
+  if (!gKeyedScalarStorageMap.Get(storageId, &scalarStorage)) {
+    scalarStorage = new KeyedScalarStorageMapType();
+    gKeyedScalarStorageMap.Put(storageId, scalarStorage);
+  }
+
+  if (scalarStorage->Get(id, &scalar)) {
     *aRet = scalar;
     return NS_OK;
   }
 
-  const ScalarInfo &info = gScalars[id];
-
   if (IsExpiredVersion(info.expiration())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // We don't currently support keyed string scalars. Disable them.
   if (info.kind == nsITelemetry::SCALAR_STRING) {
     MOZ_ASSERT(false, "Keyed string scalars are not currently supported.");
     return NS_ERROR_INVALID_ARG;
   }
 
   scalar = new KeyedScalar(info.kind);
   if (!scalar) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  gKeyedScalarStorageMap.Put(id, scalar);
-
+  scalarStorage->Put(id, scalar);
   *aRet = scalar;
   return NS_OK;
 }
 
 /**
  * Get a keyed scalar object by its enum id, if we're allowed to record it.
  *
  * @param aId The scalar id.
  * @return The KeyedScalar instance or nullptr if we're not allowed to record
  *         the scalar.
  */
 KeyedScalar*
 internal_GetRecordableKeyedScalar(mozilla::Telemetry::ScalarID aId)
 {
   // Get the scalar by the enum (it also internally checks for aId validity).
   KeyedScalar* scalar = nullptr;
-  nsresult rv = internal_GetKeyedScalarByEnum(aId, &scalar);
+  nsresult rv = internal_GetKeyedScalarByEnum(aId, GeckoProcessType_Default, &scalar);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   if (!internal_IsKeyedScalar(aId)) {
     return nullptr;
   }
 
   // Are we allowed to record this scalar?
-  if (!internal_CanRecordForScalarID(aId)) {
+  if (!internal_CanRecordForScalarID(aId) || !internal_CanRecordProcess(aId)) {
     return nullptr;
   }
 
   return scalar;
 }
 
 } // namespace
 
@@ -1169,28 +1260,40 @@ TelemetryScalar::Add(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eAdd,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->AddValue(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->AddValue(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1233,28 +1336,40 @@ TelemetryScalar::Add(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eAdd, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->AddValue(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->AddValue(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1266,16 +1381,29 @@ TelemetryScalar::Add(const nsACString& a
  * @param aId The scalar enum id.
  * @param aVal The numeric value to add to the scalar.
  */
 void
 TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eAdd,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->AddValue(aValue);
 }
 
@@ -1287,16 +1415,29 @@ TelemetryScalar::Add(mozilla::Telemetry:
  * @param aVal The numeric value to add to the scalar.
  */
 void
 TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eAdd, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->AddValue(aKey, aValue);
 }
 
@@ -1335,28 +1476,40 @@ TelemetryScalar::Set(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSet,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetValue(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetValue(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1399,28 +1552,40 @@ TelemetryScalar::Set(const nsACString& a
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eSet, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetValue(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetValue(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1432,16 +1597,29 @@ TelemetryScalar::Set(const nsACString& a
  * @param aId The scalar enum id.
  * @param aValue The numeric, unsigned value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1451,16 +1629,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aId The scalar enum id.
  * @param aValue The string value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1470,16 +1661,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aId The scalar enum id.
  * @param aValue The boolean value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSet,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aValue);
 }
 
@@ -1491,16 +1695,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aValue The numeric, unsigned value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSet, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aKey, aValue);
 }
 
@@ -1512,16 +1729,29 @@ TelemetryScalar::Set(mozilla::Telemetry:
  * @param aValue The boolean value to set the scalar to.
  */
 void
 TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                      bool aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSet, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetValue(aKey, aValue);
 }
 
@@ -1560,28 +1790,40 @@ TelemetryScalar::SetMaximum(const nsACSt
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    ScalarBase* scalar = nullptr;
-    rv = internal_GetScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildScalarAction(id, info.kind, ScalarActionType::eSetMaximum,
+                                                         unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      ScalarBase* scalar = nullptr;
+      rv = internal_GetScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetMaximum(unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetMaximum(unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1624,28 +1866,40 @@ TelemetryScalar::SetMaximum(const nsACSt
       return NS_ERROR_ILLEGAL_VALUE;
     }
 
     // Are we allowed to record this scalar?
     if (!internal_CanRecordForScalarID(id)) {
       return NS_OK;
     }
 
-    // Finally get the scalar.
-    KeyedScalar* scalar = nullptr;
-    rv = internal_GetKeyedScalarByEnum(id, &scalar);
-    if (NS_FAILED(rv)) {
-      // Don't throw on expired scalars.
-      if (rv == NS_ERROR_NOT_AVAILABLE) {
+    if (internal_CanRecordProcess(id)) {
+      // Accumulate in the child process if needed.
+      if (!XRE_IsParentProcess()) {
+        const ScalarInfo &info = gScalars[static_cast<uint32_t>(id)];
+        TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+          id, aKey, info.kind, ScalarActionType::eSetMaximum, unpackedVal);
         return NS_OK;
       }
-      return rv;
+
+      // Finally get the scalar.
+      KeyedScalar* scalar = nullptr;
+      rv = internal_GetKeyedScalarByEnum(id, GeckoProcessType_Default, &scalar);
+      if (NS_FAILED(rv)) {
+        // Don't throw on expired scalars.
+        if (rv == NS_ERROR_NOT_AVAILABLE) {
+          return NS_OK;
+        }
+        return rv;
+      }
+
+      sr = scalar->SetMaximum(aKey, unpackedVal);
+    } else {
+      sr = ScalarResult::CannotRecordInProcess;
     }
-
-    sr = scalar->SetMaximum(aKey, unpackedVal);
   }
 
   // Warn the user about the error if we need to.
   if (internal_ShouldLogError(sr)) {
     internal_LogScalarError(aName, sr);
   }
 
   return MapToNsResult(sr);
@@ -1657,16 +1911,29 @@ TelemetryScalar::SetMaximum(const nsACSt
  * @param aId The scalar enum id.
  * @param aValue The numeric value to set the scalar to.
  */
 void
 TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildScalarAction(aId, info.kind, ScalarActionType::eSetMaximum,
+                                                     scalarValue);
+    return;
+  }
+
   ScalarBase* scalar = internal_GetRecordableScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetMaximum(aValue);
 }
 
@@ -1678,198 +1945,273 @@ TelemetryScalar::SetMaximum(mozilla::Tel
  * @param aValue The numeric value to set the scalar to.
  */
 void
 TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey,
                             uint32_t aValue)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
 
+  // Accumulate in the child process if needed.
+  if (!XRE_IsParentProcess()) {
+    nsCOMPtr<nsIVariant> scalarValue;
+    nsresult rv = GetVariant(aValue, scalarValue);
+    if (NS_FAILED(rv)) {
+      return;
+    }
+    const ScalarInfo &info = gScalars[static_cast<uint32_t>(aId)];
+    TelemetryIPCAccumulator::RecordChildKeyedScalarAction(
+      aId, aKey, info.kind, ScalarActionType::eSetMaximum, scalarValue);
+    return;
+  }
+
   KeyedScalar* scalar = internal_GetRecordableKeyedScalar(aId);
   if (!scalar) {
     return;
   }
 
   scalar->SetMaximum(aKey, aValue);
 }
 
 /**
  * Serializes the scalars from the given dataset to a json-style object and resets them.
- * The returned structure looks like {"group1.probe":1,"group1.other_probe":false,...}.
+ * The returned structure looks like:
+ *    {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }.
  *
  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
  * @param aClear Whether to clear out the scalars after snapshotting.
  */
 nsresult
 TelemetryScalar::CreateSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
                                  uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
 {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Snapshotting scalars should only happen in the parent processes.");
   // If no arguments were passed in, apply the default value.
   if (!optional_argc) {
     aClearScalars = false;
   }
 
   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
   if (!root_obj) {
     return NS_ERROR_FAILURE;
   }
   aResult.setObject(*root_obj);
 
+  // Return `{}` in child processes.
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+
   // Only lock the mutex while accessing our data, without locking any JS related code.
   typedef mozilla::Pair<const char*, nsCOMPtr<nsIVariant>> DataPair;
-  nsTArray<DataPair> scalarsToReflect;
+  typedef nsTArray<DataPair> ScalarArray;
+  nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     // Iterate the scalars in gScalarStorageMap. The storage may contain empty or yet to be
-    // initialized scalars.
+    // initialized scalars from all the supported processes.
     for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-      ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
+      ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
+      ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
 
-      // Get the informations for this scalar.
-      const ScalarInfo& info = gScalars[iter.Key()];
+      // Iterate each available child storage.
+      for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+        ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
+
+        // Get the informations for this scalar.
+        const ScalarInfo& info = gScalars[childIter.Key()];
 
-      // Serialize the scalar if it's in the desired dataset.
-      if (IsInDataset(info.dataset, aDataset)) {
-        // Get the scalar value.
-        nsCOMPtr<nsIVariant> scalarValue;
-        nsresult rv = scalar->GetValue(scalarValue);
-        if (NS_FAILED(rv)) {
-          return rv;
+        // Serialize the scalar if it's in the desired dataset.
+        if (IsInDataset(info.dataset, aDataset)) {
+          // Get the scalar value.
+          nsCOMPtr<nsIVariant> scalarValue;
+          nsresult rv = scalar->GetValue(scalarValue);
+          if (NS_FAILED(rv)) {
+            return rv;
+          }
+          // Append it to our list.
+          processScalars.AppendElement(mozilla::MakePair(info.name(), scalarValue));
         }
-        // Append it to our list.
-        scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarValue));
       }
     }
 
     if (aClearScalars) {
       // The map already takes care of freeing the allocated memory.
       gScalarStorageMap.Clear();
     }
   }
 
   // Reflect it to JS.
-  for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) {
-    const DataPair& scalar = scalarsToReflect[i];
+  for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
+    ScalarArray& processScalars = iter.Data();
+    const char* processName =
+      XRE_ChildProcessTypeToString(static_cast<GeckoProcessType>(iter.Key()));
 
-    // Convert it to a JS Val.
-    JS::Rooted<JS::Value> scalarJsValue(aCx);
-    nsresult rv =
-      nsContentUtils::XPConnect()->VariantToJS(aCx, root_obj, scalar.second(), &scalarJsValue);
-    if (NS_FAILED(rv)) {
-      return rv;
+    // Create the object that will hold the scalars for this process and add it
+    // to the returned root object.
+    JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
+    if (!processObj ||
+        !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
     }
 
-    // Add it to the scalar object.
-    if (!JS_DefineProperty(aCx, root_obj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
+    for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length(); i++) {
+      const DataPair& scalar = processScalars[i];
+
+      // Convert it to a JS Val.
+      JS::Rooted<JS::Value> scalarJsValue(aCx);
+      nsresult rv =
+        nsContentUtils::XPConnect()->VariantToJS(aCx, processObj, scalar.second(), &scalarJsValue);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      // Add it to the scalar object.
+      if (!JS_DefineProperty(aCx, processObj, scalar.first(), scalarJsValue, JSPROP_ENUMERATE)) {
+        return NS_ERROR_FAILURE;
+      }
     }
   }
 
   return NS_OK;
 }
 
 /**
  * Serializes the scalars from the given dataset to a json-style object and resets them.
  * The returned structure looks like:
- *   { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }
+ *   { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
  *
  * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
  * @param aClear Whether to clear out the keyed scalars after snapshotting.
  */
 nsresult
 TelemetryScalar::CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, JSContext* aCx,
                                       uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult)
 {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Snapshotting scalars should only happen in the parent processes.");
   // If no arguments were passed in, apply the default value.
   if (!optional_argc) {
     aClearScalars = false;
   }
 
   JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
   if (!root_obj) {
     return NS_ERROR_FAILURE;
   }
   aResult.setObject(*root_obj);
 
+  // Return `{}` in child processes.
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+
   // Only lock the mutex while accessing our data, without locking any JS related code.
   typedef mozilla::Pair<const char*, nsTArray<KeyedScalar::KeyValuePair>> DataPair;
-  nsTArray<DataPair> scalarsToReflect;
+  typedef nsTArray<DataPair> ScalarArray;
+  nsDataHashtable<ProcessIDHashKey, ScalarArray> scalarsToReflect;
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     // Iterate the scalars in gKeyedScalarStorageMap. The storage may contain empty or yet
-    // to be initialized scalars.
+    // to be initialized scalars from all the supported processes.
     for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-      KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
+      KeyedScalarStorageMapType* scalarStorage =
+        static_cast<KeyedScalarStorageMapType*>(iter.Data());
+      ScalarArray& processScalars = scalarsToReflect.GetOrInsert(iter.Key());
 
-      // Get the informations for this scalar.
-      const ScalarInfo& info = gScalars[iter.Key()];
+      for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+        KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
+
+        // Get the informations for this scalar.
+        const ScalarInfo& info = gScalars[childIter.Key()];
 
-      // Serialize the scalar if it's in the desired dataset.
-      if (IsInDataset(info.dataset, aDataset)) {
-        // Get the keys for this scalar.
-        nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
-        nsresult rv = scalar->GetValue(scalarKeyedData);
-        if (NS_FAILED(rv)) {
-          return rv;
+        // Serialize the scalar if it's in the desired dataset.
+        if (IsInDataset(info.dataset, aDataset)) {
+          // Get the keys for this scalar.
+          nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData;
+          nsresult rv = scalar->GetValue(scalarKeyedData);
+          if (NS_FAILED(rv)) {
+            return rv;
+          }
+          // Append it to our list.
+          processScalars.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
         }
-        // Append it to our list.
-        scalarsToReflect.AppendElement(mozilla::MakePair(info.name(), scalarKeyedData));
       }
     }
 
     if (aClearScalars) {
       // The map already takes care of freeing the allocated memory.
       gKeyedScalarStorageMap.Clear();
     }
   }
 
   // Reflect it to JS.
-  for (nsTArray<DataPair>::size_type i = 0; i < scalarsToReflect.Length(); i++) {
-    const DataPair& keyedScalarData = scalarsToReflect[i];
+  for (auto iter = scalarsToReflect.Iter(); !iter.Done(); iter.Next()) {
+    ScalarArray& processScalars = iter.Data();
+    const char* processName =
+      XRE_ChildProcessTypeToString(static_cast<GeckoProcessType>(iter.Key()));
 
-    // Go through each keyed scalar and create a keyed scalar object.
-    // This object will hold the values for all the keyed scalar keys.
-    JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
+    // Create the object that will hold the scalars for this process and add it
+    // to the returned root object.
+    JS::RootedObject processObj(aCx, JS_NewPlainObject(aCx));
+    if (!processObj ||
+        !JS_DefineProperty(aCx, root_obj, processName, processObj, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    for (nsTArray<DataPair>::size_type i = 0; i < processScalars.Length(); i++) {
+      const DataPair& keyedScalarData = processScalars[i];
+
+      // Go through each keyed scalar and create a keyed scalar object.
+      // This object will hold the values for all the keyed scalar keys.
+      JS::RootedObject keyedScalarObj(aCx, JS_NewPlainObject(aCx));
 
-    // Define a property for each scalar key, then add it to the keyed scalar
-    // object.
-    const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second();
-    for (uint32_t i = 0; i < keyProps.Length(); i++) {
-      const KeyedScalar::KeyValuePair& keyData = keyProps[i];
+      // Define a property for each scalar key, then add it to the keyed scalar
+      // object.
+      const nsTArray<KeyedScalar::KeyValuePair>& keyProps = keyedScalarData.second();
+      for (uint32_t i = 0; i < keyProps.Length(); i++) {
+        const KeyedScalar::KeyValuePair& keyData = keyProps[i];
 
-      // Convert the value for the key to a JSValue.
-      JS::Rooted<JS::Value> keyJsValue(aCx);
-      nsresult rv =
-        nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
-      if (NS_FAILED(rv)) {
-        return rv;
+        // Convert the value for the key to a JSValue.
+        JS::Rooted<JS::Value> keyJsValue(aCx);
+        nsresult rv =
+          nsContentUtils::XPConnect()->VariantToJS(aCx, keyedScalarObj, keyData.second(), &keyJsValue);
+        if (NS_FAILED(rv)) {
+          return rv;
+        }
+
+        // Add the key to the scalar representation.
+        const NS_ConvertUTF8toUTF16 key(keyData.first());
+        if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
+          return NS_ERROR_FAILURE;
+        }
       }
 
-      // Add the key to the scalar representation.
-      const NS_ConvertUTF8toUTF16 key(keyData.first());
-      if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), keyJsValue, JSPROP_ENUMERATE)) {
+      // Add the scalar to the root object.
+      if (!JS_DefineProperty(aCx, processObj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) {
         return NS_ERROR_FAILURE;
       }
     }
-
-    // Add the scalar to the root object.
-    if (!JS_DefineProperty(aCx, root_obj, keyedScalarData.first(), keyedScalarObj, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
-    }
   }
 
   return NS_OK;
 }
 
 /**
  * Resets all the stored scalars. This is intended to be only used in tests.
  */
 void
 TelemetryScalar::ClearScalars()
 {
+  MOZ_ASSERT(XRE_IsParentProcess(), "Scalars should only be cleared in the parent process.");
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   gScalarStorageMap.Clear();
   gKeyedScalarStorageMap.Clear();
 }
 
 size_t
 TelemetryScalar::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
@@ -1877,20 +2219,125 @@ TelemetryScalar::GetMapShallowSizesOfExc
   return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
 }
 
 size_t
 TelemetryScalar::GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryScalarsMutex);
   size_t n = 0;
-  // For the plain scalars...
+  // Account for scalar data coming from parent and child processes.
   for (auto iter = gScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-    ScalarBase* scalar = static_cast<ScalarBase*>(iter.Data());
-    n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    ScalarStorageMapType* scalarStorage = static_cast<ScalarStorageMapType*>(iter.Data());
+    for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+      ScalarBase* scalar = static_cast<ScalarBase*>(childIter.Data());
+      n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    }
   }
-  // ...and for the keyed scalars.
+  // Also account for keyed scalar data coming from parent and child processes.
   for (auto iter = gKeyedScalarStorageMap.Iter(); !iter.Done(); iter.Next()) {
-    KeyedScalar* scalar = static_cast<KeyedScalar*>(iter.Data());
-    n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    KeyedScalarStorageMapType* scalarStorage =
+      static_cast<KeyedScalarStorageMapType*>(iter.Data());
+    for (auto childIter = scalarStorage->Iter(); !childIter.Done(); childIter.Next()) {
+      KeyedScalar* scalar = static_cast<KeyedScalar*>(childIter.Data());
+      n += scalar->SizeOfIncludingThis(aMallocSizeOf);
+    }
   }
   return n;
 }
+
+void
+TelemetryScalar::UpdateChildData(GeckoProcessType aProcessType,
+                                 const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "The stored child processes scalar data must be updated from the parent process.");
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    if (internal_IsKeyedScalar(upd.mId)) {
+      continue;
+    }
+
+    // Are we allowed to record this scalar? We don't need to check for
+    // allowed processes here, that's taken care of when recording
+    // in child processes.
+    if (!internal_CanRecordForScalarID(upd.mId)) {
+      continue;
+    }
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    ScalarBase* scalar = nullptr;
+    nsresult rv = internal_GetScalarByEnum(upd.mId, aProcessType, &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        scalar->SetValue(upd.mData);
+        break;
+      case ScalarActionType::eAdd:
+        scalar->AddValue(upd.mData);
+        break;
+      case ScalarActionType::eSetMaximum:
+        scalar->SetMaximum(upd.mData);
+        break;
+      default:
+        NS_WARNING("Unsupported action coming from scalar child updates.");
+    }
+  }
+}
+
+void
+TelemetryScalar::UpdateChildKeyedData(GeckoProcessType aProcessType,
+                                      const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions)
+{
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "The stored child processes keyed scalar data must be updated from the parent process.");
+  StaticMutexAutoLock locker(gTelemetryScalarsMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+
+  for (auto& upd : aScalarActions) {
+    if (!internal_IsKeyedScalar(upd.mId)) {
+      continue;
+    }
+
+    // Are we allowed to record this scalar? We don't need to check for
+    // allowed processes here, that's taken care of when recording
+    // in child processes.
+    if (!internal_CanRecordForScalarID(upd.mId)) {
+      continue;
+    }
+
+    // Refresh the data in the parent process with the data coming from the child
+    // processes.
+    KeyedScalar* scalar = nullptr;
+    nsresult rv = internal_GetKeyedScalarByEnum(upd.mId, aProcessType, &scalar);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD");
+      continue;
+    }
+
+    switch (upd.mActionType)
+    {
+      case ScalarActionType::eSet:
+        scalar->SetValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      case ScalarActionType::eAdd:
+        scalar->AddValue(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      case ScalarActionType::eSetMaximum:
+        scalar->SetMaximum(NS_ConvertUTF8toUTF16(upd.mKey), upd.mData);
+        break;
+      default:
+        NS_WARNING("Unsupported action coming from keyed scalar child updates.");
+    }
+  }
+}
--- a/toolkit/components/telemetry/TelemetryScalar.h
+++ b/toolkit/components/telemetry/TelemetryScalar.h
@@ -54,11 +54,17 @@ void Set(mozilla::Telemetry::ScalarID aI
 void SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, uint32_t aValue);
 
 // Only to be used for testing.
 void ClearScalars();
 
 size_t GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 size_t GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
+void UpdateChildData(GeckoProcessType aProcessType,
+                     const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions);
+
+void UpdateChildKeyedData(GeckoProcessType aProcessType,
+                          const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions);
+
 } // namespace TelemetryScalar
 
 #endif // TelemetryScalar_h__
\ No newline at end of file
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -42,16 +42,22 @@ const REASON_ENVIRONMENT_CHANGE = "envir
 const REASON_SHUTDOWN = "shutdown";
 
 const HISTOGRAM_SUFFIXES = {
   PARENT: "",
   CONTENT: "#content",
   GPU: "#gpu",
 }
 
+const INTERNAL_PROCESSES_NAMES = {
+  PARENT: "default",
+  CONTENT: "tab",
+  GPU: "gpu",
+}
+
 const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
 
 const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
 const MIN_SUBSESSION_LENGTH_MS = Preferences.get("toolkit.telemetry.minSubsessionLength", 10 * 60) * 1000;
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");
 
@@ -973,17 +979,19 @@ var Impl = {
     return ret;
   },
 
   /**
    * Get a snapshot of the scalars and clear them.
    * @param {subsession} If true, then we collect the data for a subsession.
    * @param {clearSubsession} If true, we  need to clear the subsession.
    * @param {keyed} Take a snapshot of keyed or non keyed scalars.
-   * @return {Object} The scalar data as a Javascript object.
+   * @return {Object} The scalar data as a Javascript object, including the
+   *         data from child processes, in the following format:
+   *            {'content': { 'scalarName': ... }, 'gpu': { ... } }
    */
   getScalars(subsession, clearSubsession, keyed) {
     this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " +
                     clearSubsession + ", keyed: " + keyed);
 
     if (!subsession) {
       // We only support scalars for subsessions.
       this._log.trace("getScalars - We only support scalars in subsessions.");
@@ -991,21 +999,27 @@ var Impl = {
     }
 
     let scalarsSnapshot = keyed ?
       Telemetry.snapshotKeyedScalars(this.getDatasetType(), clearSubsession) :
       Telemetry.snapshotScalars(this.getDatasetType(), clearSubsession);
 
     // Don't return the test scalars.
     let ret = {};
-    for (let name in scalarsSnapshot) {
-      if (name.startsWith('telemetry.test') && this._testing == false) {
-        this._log.trace("getScalars - Skipping test scalar: " + name);
-      } else {
-        ret[name] = scalarsSnapshot[name];
+    for (let processName in scalarsSnapshot) {
+      for (let name in scalarsSnapshot[processName]) {
+        if (name.startsWith('telemetry.test') && this._testing == false) {
+          this._log.trace("getScalars - Skipping test scalar: " + name);
+          continue;
+        }
+        // Finally arrange the data in the returned object.
+        if (!(processName in ret)) {
+          ret[processName] = {};
+        }
+        ret[processName][name] = scalarsSnapshot[processName][name];
       }
     }
 
     return ret;
   },
 
   getEvents(isSubsession, clearSubsession) {
     if (!isSubsession) {
@@ -1282,34 +1296,43 @@ var Impl = {
 
     if (Utils.isContentProcess) {
       return payloadObj;
     }
 
     // Additional payload for chrome process.
     let histograms = protect(() => this.getHistograms(isSubsession, clearSubsession), {});
     let keyedHistograms = protect(() => this.getKeyedHistograms(isSubsession, clearSubsession), {});
+    let scalars = protect(() => this.getScalars(isSubsession, clearSubsession), {});
+    let keyedScalars = protect(() => this.getScalars(isSubsession, clearSubsession, true), {});
+
     payloadObj.histograms = histograms[HISTOGRAM_SUFFIXES.PARENT] || {};
     payloadObj.keyedHistograms = keyedHistograms[HISTOGRAM_SUFFIXES.PARENT] || {};
     payloadObj.processes = {
       parent: {
-        scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
-        keyedScalars: protect(() => this.getScalars(isSubsession, clearSubsession, true)),
+        scalars: scalars[INTERNAL_PROCESSES_NAMES.PARENT] || {},
+        keyedScalars: keyedScalars[INTERNAL_PROCESSES_NAMES.PARENT] || {},
         events: protect(() => this.getEvents(isSubsession, clearSubsession)),
       },
       content: {
+        scalars: scalars[INTERNAL_PROCESSES_NAMES.CONTENT],
+        keyedScalars: keyedScalars[INTERNAL_PROCESSES_NAMES.CONTENT],
         histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT],
         keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.CONTENT],
       },
     };
 
     // Only include the GPU process if we've accumulated data for it.
     if (HISTOGRAM_SUFFIXES.GPU in histograms ||
-        HISTOGRAM_SUFFIXES.GPU in keyedHistograms) {
+        HISTOGRAM_SUFFIXES.GPU in keyedHistograms ||
+        INTERNAL_PROCESSES_NAMES.GPU in scalars ||
+        INTERNAL_PROCESSES_NAMES.GPU in keyedScalars) {
       payloadObj.processes.gpu = {
+        scalars: scalars[INTERNAL_PROCESSES_NAMES.GPU],
+        keyedScalars: keyedScalars[INTERNAL_PROCESSES_NAMES.GPU],
         histograms: histograms[HISTOGRAM_SUFFIXES.GPU],
         keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.GPU],
       };
     }
 
     payloadObj.info = info;
 
     // Add extended set measurements for chrome process.
--- a/toolkit/components/telemetry/docs/collection/scalars.rst
+++ b/toolkit/components/telemetry/docs/collection/scalars.rst
@@ -1,16 +1,15 @@
 =======
 Scalars
 =======
 
 Historically we started to overload our histogram mechanism to also collect scalar data,
 such as flag values, counts, labels and others.
 The scalar measurement types are the suggested way to collect that kind of scalar data.
-We currently only support recording of scalars from the parent process.
 The serialized scalar data is submitted with the :doc:`main pings <../data/main-ping>`.
 
 The API
 =======
 Scalar probes can be managed either through the `nsITelemetry interface <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/nsITelemetry.idl>`_
 or the `C++ API <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Telemetry.h>`_.
 
 JS API
@@ -102,28 +101,40 @@ Required Fields
 - ``notification_emails``: A list of email addresses to notify with alerts of expiring probes. More importantly, these are used by the data steward to verify that the probe is still useful.
 
 Optional Fields
 ---------------
 
 - ``cpp_guard``: A string that gets inserted as an ``#ifdef`` directive around the automatically generated C++ declaration. This is typically used for platform-specific scalars, e.g. ``ANDROID``.
 - ``release_channel_collection``: This can be either ``opt-in`` (default) or ``opt-out``. With the former the scalar is submitted by default on pre-release channels; on the release channel only if the user opted into additional data collection. With the latter the scalar is submitted by default on release and pre-release channels, unless the user opted out.
 - ``keyed``: A boolean that determines whether this is a keyed scalar. It defaults to ``False``.
+- ``record_in_processes``: A list of processes the scalar is allowed to record in. Currently supported values are:
+
+  - ``main`` (the default value);
+  - ``content``;
+  - ``gpu``;
+  - ``all_child`` (record in all the child processes);
+  - ``all`` (record in all the processes).
 
 String type restrictions
 ------------------------
 To prevent abuses, the content of a string scalar is limited to 50 characters in length. Trying
 to set a longer string will result in an error and no string being set.
 
 Keyed Scalars
 -------------
 Keyed scalars are collections of one of the available scalar types, indexed by a string key that can contain UTF8 characters and cannot be longer than 70 characters. Keyed scalars can contain up to 100 keys. This scalar type is for example useful when you want to break down certain counts by a name, like how often searches happen with which search engine.
 
 Keyed scalars should only be used if the set of keys are not known beforehand. If the keys are from a known set of strings, other options are preferred if suitable, like categorical histograms or splitting measurements up into separate scalars.
 
+Multiple processes caveats
+--------------------------
+When recording data in different processes of the same type (e.g. multiple content processes), the user is responsible for preventing races between the operations on the scalars.
+Races can happen because scalar changes are send from each child process to the parent process, and then merged into the final storage location. Since there's no synchronization between the processes, operations like ``setMaximum`` can potentially produce different results if sent from more than one child process.
+
 The processor scripts
 =====================
 The scalar definition file is processed and checked for correctness at compile time. If it
 conforms to the specification, the processor scripts generate two C++ headers files, included
 by the Telemetry C++ core.
 
 gen-scalar-data.py
 ------------------
@@ -133,8 +144,15 @@ This header file contains an array holdi
 to an array of ``ScalarInfo`` structures representing all the scalars.
 
 gen-scalar-enum.py
 ------------------
 This script is called by the build system to generate the ``TelemetryScalarEnums.h`` C++ header
 file out of the scalar definitions.
 This header file contains an enum class with all the scalar identifiers used to access them
 from code through the C++ API.
+
+Version History
+===============
+
+- Firefox 50: Initial scalar support (`bug 1276195 <https://bugzilla.mozilla.org/show_bug.cgi?id=1276195>`_).
+- Firefox 51: Added keyed scalars (`bug 1277806 <https://bugzilla.mozilla.org/show_bug.cgi?id=1277806>`_).
+- Firefox 53: Added child process scalars (`bug 1278556 <https://bugzilla.mozilla.org/show_bug.cgi?id=1278556>`_).
--- a/toolkit/components/telemetry/docs/data/main-ping.rst
+++ b/toolkit/components/telemetry/docs/data/main-ping.rst
@@ -100,32 +100,38 @@ This section contains per-process data.
 Structure:
 
 .. code-block:: js
 
     "processes" : {
       ... other processes ...
       "parent": {
         scalars: {...},
+        keyedScalars: {...},
       },
       "content": {
+        scalars: {...},
+        keyedScalars: {...},
         histograms: {...},
         keyedHistograms: {...},
       },
+      "gpu": {
+        ...
+      }
     }
 
 histograms and keyedHistograms
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 This section contains histograms and keyed histograms accumulated on content processes. Histograms recorded on a content child process have different character than parent histograms. For instance, ``GC_MS`` will be much different in ``processes.content`` as it has to contend with web content, whereas the instance in ``payload.histograms`` has only to contend with browser JS. Also, some histograms may be absent if never recorded on a content child process (``EVENTLOOP_UI_ACTIVITY`` is parent-process-only).
 
 This format was adopted in Firefox 51 via bug 1218576.
 
-scalars
-~~~~~~~
-This section contains the :doc:`../collection/scalars` that are valid for the current platform. Scalars are not created nor submitted if no data was added to them, and are only reported with subsession pings. Scalar data is only currently reported for the main process. Their type and format is described by the ``Scalars.yaml`` file. Its most recent version is available `here <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_. The ``info.revision`` field indicates the revision of the file that describes the reported scalars.
+scalars and keyedScalars
+~~~~~~~~~~~~~~~~~~~~~~~~
+This section contains the :doc:`../collection/scalars` that are valid for the current platform. Scalars are only submitted if if data was added to them, and are only reported with subsession pings. The record scalars are described in the `Scalars.yaml <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_ file. The ``info.revision`` field indicates the revision of the file that describes the reported scalars.
 
 childPayloads
 -------------
 The Telemetry payloads sent by child processes, recorded on child process shutdown (event ``content-child-shutdown`` observed). They are reduced session payloads, only available with e10s. Among some other things, they don't contain histograms, keyed histograms, addon details, addon histograms, or UI Telemetry.
 
 Note: Child payloads are not collected and cleared with subsession splits, they are currently only meaningful when analysed from ``saved-session`` or ``main`` pings with ``reason`` set to ``shutdown``.
 
 Note: Before Firefox 51 and bug 1218576, content process histograms and keyedHistograms were in the individual child payloads instead of being aggregated into ``processes.content``.
--- a/toolkit/components/telemetry/gen-scalar-data.py
+++ b/toolkit/components/telemetry/gen-scalar-data.py
@@ -35,21 +35,22 @@ def write_scalar_info(scalar, output, na
     :param output: the output stream.
     :param name_index: the index of the scalar name in the strings table.
     :param expiration_index: the index of the expiration version in the strings table.
     """
     cpp_guard = scalar.cpp_guard
     if cpp_guard:
         print("#if defined(%s)" % cpp_guard, file=output)
 
-    print("  {{ {}, {}, {}, {}, {} }},"\
+    print("  {{ {}, {}, {}, {}, {}, {} }},"\
           .format(scalar.nsITelemetry_kind,
                   name_index,
                   expiration_index,
                   scalar.dataset,
+                  " | ".join(scalar.record_in_processes_enum),
                   "true" if scalar.keyed else "false"),
           file=output)
 
     if cpp_guard:
         print("#endif", file=output)
 
 def write_scalar_tables(scalars, output):
     """Writes the scalar and strings tables to an header file.
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -44,16 +44,17 @@ EXPORTS.mozilla += [
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
     'TelemetryCommon.cpp',
     'TelemetryEvent.cpp',
     'TelemetryHistogram.cpp',
+    'TelemetryIPCAccumulator.cpp',
     'TelemetryScalar.cpp',
     'WebrtcTelemetry.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'TelemetryStartup.js',
     'TelemetryStartup.manifest'
 ]
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -405,17 +405,17 @@ interface nsITelemetry : nsISupports
    * @param aValue The numeric value to set the scalar to. Only unsigned integers supported.
    */
   [implicit_jscontext]
   void scalarSetMaximum(in ACString aName, in jsval aValue);
 
   /**
    * Serializes the scalars from the given dataset to a JSON-style object and resets them.
    * The returned structure looks like:
-   *   { "group1.probe": 1, "group1.other_probe": false, ... }
+   *   {"process": {"group1.probe":1,"group1.other_probe":false,...}, ... }.
    *
    * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    * @param [aClear=false] Whether to clear out the scalars after snapshotting.
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**
@@ -448,17 +448,17 @@ interface nsITelemetry : nsISupports
    */
   [implicit_jscontext]
   void keyedScalarSetMaximum(in ACString aName, in AString aKey, in jsval aValue);
 
   /**
    * Serializes the keyed scalars from the given dataset to a JSON-style object and
    * resets them.
    * The returned structure looks like:
-   *   { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }
+   *   { "process": { "group1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... }
    *
    * @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
    * @param [aClear=false] Whether to clear out the scalars after snapshotting.
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotKeyedScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**
--- a/toolkit/components/telemetry/parse_scalars.py
+++ b/toolkit/components/telemetry/parse_scalars.py
@@ -9,16 +9,26 @@ from shared_telemetry_utils import add_e
 # The map of containing the allowed scalar types and their mapping to
 # nsITelemetry::SCALAR_* type constants.
 SCALAR_TYPES_MAP = {
     'uint': 'nsITelemetry::SCALAR_COUNT',
     'string': 'nsITelemetry::SCALAR_STRING',
     'boolean': 'nsITelemetry::SCALAR_BOOLEAN'
 }
 
+# This is a list of flags that determine which process the scalar is allowed
+# to record from.
+KNOWN_PROCESS_FLAGS = {
+    'all': 'RecordedProcessType::All',
+    'all_childs': 'RecordedProcessType::AllChilds',
+    'main': 'RecordedProcessType::Main',
+    'content': 'RecordedProcessType::Content',
+    'gpu': 'RecordedProcessType::Gpu',
+}
+
 class ScalarType:
     """A class for representing a scalar definition."""
 
     def __init__(self, group_name, probe_name, definition):
         # Validate and set the name, so we don't need to pass it to the other
         # validation functions.
         self.validate_names(group_name, probe_name)
         self._name = probe_name
@@ -82,23 +92,25 @@ class ScalarType:
             'expires': basestring,
             'kind': basestring,
             'notification_emails': list # This contains strings. See LIST_FIELDS_CONTENT.
         }
 
         OPTIONAL_FIELDS = {
             'cpp_guard': basestring,
             'release_channel_collection': basestring,
-            'keyed': bool
+            'keyed': bool,
+            'record_in_processes': list,
         }
 
         # The types for the data within the fields that hold lists.
         LIST_FIELDS_CONTENT = {
             'bug_numbers': int,
-            'notification_emails': basestring
+            'notification_emails': basestring,
+            'record_in_processes': basestring,
         }
 
         # Concatenate the required and optional field definitions.
         ALL_FIELDS = REQUIRED_FIELDS.copy()
         ALL_FIELDS.update(OPTIONAL_FIELDS)
 
         # Checks that all the required fields are available.
         missing_fields = [f for f in REQUIRED_FIELDS.keys() if f not in definition]
@@ -148,16 +160,22 @@ class ScalarType:
         if collection_policy and collection_policy not in ['opt-in', 'opt-out']:
             raise ValueError(self._name + ' - unknown collection policy: ' + collection_policy)
 
         # Validate the cpp_guard.
         cpp_guard = definition.get('cpp_guard')
         if cpp_guard and re.match(r'\W', cpp_guard):
             raise ValueError(self._name + ' - invalid cpp_guard: ' + cpp_guard)
 
+        # Validate record_in_processes.
+        record_in_processes = definition.get('record_in_processes', [])
+        for proc in record_in_processes:
+            if proc not in KNOWN_PROCESS_FLAGS.keys():
+                raise ValueError(self._name + ' - unknown value in record_in_processes: ' + proc)
+
     @property
     def name(self):
         """Get the scalar name"""
         return self._name
 
     @property
     def label(self):
         """Get the scalar label generated from the scalar and group names."""
@@ -204,16 +222,26 @@ class ScalarType:
         return SCALAR_TYPES_MAP.get(self.kind)
 
     @property
     def notification_emails(self):
         """Get the list of notification emails"""
         return self._definition['notification_emails']
 
     @property
+    def record_in_processes(self):
+        """Get the non-empty list of processes to record data in"""
+        return self._definition.get('record_in_processes', ['main'])
+
+    @property
+    def record_in_processes_enum(self):
+        """Get the non-empty list of flags representing the processes to record data in"""
+        return [KNOWN_PROCESS_FLAGS.get(p) for p in self.record_in_processes]
+
+    @property
     def dataset(self):
         """Get the nsITelemetry constant equivalent to the chose release channel collection
         policy for the scalar.
         """
         # The collection policy is optional, but we still define a default
         # behaviour for it.
         release_channel_collection = \
             self._definition.get('release_channel_collection', 'opt-in')
--- a/toolkit/components/telemetry/tests/gtest/TestScalars.cpp
+++ b/toolkit/components/telemetry/tests/gtest/TestScalars.cpp
@@ -116,17 +116,24 @@ GetScalarsSnapshot(bool aKeyed, JSContex
     rv = telemetry->SnapshotScalars(nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN,
                                     false, aCx, 0, &scalarsSnapshot);
   }
 
   // Validate the snapshot.
   ASSERT_EQ(rv, NS_OK) << "Creating a snapshot of the data must not fail.";
   ASSERT_TRUE(scalarsSnapshot.isObject()) << "The snapshot must be an object.";
 
-  aResult.set(scalarsSnapshot);
+  // We currently only support scalars from the parent process in the gtests.
+  JS::RootedValue parentScalars(aCx);
+  JS::RootedObject scalarObj(aCx, &scalarsSnapshot.toObject());
+  // Don't complain if no scalars for the parent process can be found. Just
+  // return an empty object.
+  Unused << JS_GetProperty(aCx, scalarObj, "default", &parentScalars);
+
+  aResult.set(parentScalars);
 }
 
 } // Anonymous namespace.
 
 // Test that we can properly write unsigned scalars using the C++ API.
 TEST_F(TelemetryTestFixture, ScalarUnsigned) {
   AutoJSContextWithGlobal cx(mCleanGlobal);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_ChildScalars.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/TelemetryController.jsm", this);
+Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
+Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
+Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
+
+const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
+
+const PLATFORM_VERSION = "1.9.2";
+const APP_VERSION = "1";
+const APP_ID = "xpcshell@tests.mozilla.org";
+const APP_NAME = "XPCShell";
+
+const UINT_SCALAR = "telemetry.test.unsigned_int_kind";
+const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int";
+const KEYED_BOOL_SCALAR = "telemetry.test.keyed_boolean_kind";
+const CONTENT_ONLY_UINT_SCALAR = "telemetry.test.content_only_uint";
+const ALL_PROCESSES_UINT_SCALAR = "telemetry.test.all_processes_uint";
+const ALL_CHILD_PROCESSES_STRING_SCALAR = "telemetry.test.all_child_processes_string";
+
+function run_child_test() {
+  // Attempt to set some scalar values from the "content" process.
+  // The next scalars are not allowed to be recorded in the content process.
+  Telemetry.scalarSet(UINT_SCALAR, 1);
+  Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "should-not-be-recorded", 1);
+
+  // The next scalars shou be recorded in only the content process.
+  Telemetry.scalarSet(CONTENT_ONLY_UINT_SCALAR, 37);
+  Telemetry.scalarSet(ALL_CHILD_PROCESSES_STRING_SCALAR, "all-child-processes");
+
+  // The next scalar will be recorded in the parent and content processes.
+  Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "content-key", true);
+  Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "content-key2", false);
+  Telemetry.scalarSet(ALL_PROCESSES_UINT_SCALAR, 37);
+}
+
+function setParentScalars() {
+  // The following scalars are not allowed to be recorded in the parent process.
+  Telemetry.scalarSet(CONTENT_ONLY_UINT_SCALAR, 15);
+  Telemetry.scalarSet(ALL_CHILD_PROCESSES_STRING_SCALAR, "all-child-processes");
+
+  // The next ones will be recorded only in the parent.
+  Telemetry.scalarSet(UINT_SCALAR, 15);
+
+  // This last batch will be available both in the parent and child processes.
+  Telemetry.keyedScalarSet(KEYED_BOOL_SCALAR, "parent-key", false);
+  Telemetry.scalarSet(ALL_PROCESSES_UINT_SCALAR, 37);
+}
+
+function checkParentScalars(processData) {
+  const scalars = processData.scalars;
+  const keyedScalars = processData.keyedScalars;
+
+  // Check the plain scalars, make sure we're only recording what we expect.
+  Assert.ok(!(CONTENT_ONLY_UINT_SCALAR in scalars),
+            "Scalars must not be recorded in other processes unless allowed.");
+  Assert.ok(!(ALL_CHILD_PROCESSES_STRING_SCALAR in scalars),
+            "Scalars must not be recorded in other processes unless allowed.");
+  Assert.ok(UINT_SCALAR in scalars,
+            `${UINT_SCALAR} must be recorded in the parent process.`);
+  Assert.equal(scalars[UINT_SCALAR], 15,
+               `${UINT_SCALAR} must have the correct value (parent process).`);
+  Assert.ok(ALL_PROCESSES_UINT_SCALAR in scalars,
+            `${ALL_PROCESSES_UINT_SCALAR} must be recorded in the parent process.`);
+  Assert.equal(scalars[ALL_PROCESSES_UINT_SCALAR], 37,
+               `${ALL_PROCESSES_UINT_SCALAR} must have the correct value (parent process).`);
+
+  // Now check the keyed scalars.
+  Assert.ok(KEYED_BOOL_SCALAR in keyedScalars,
+            `${KEYED_BOOL_SCALAR} must be recorded in the parent process.`);
+  Assert.ok('parent-key' in keyedScalars[KEYED_BOOL_SCALAR],
+            `${KEYED_BOOL_SCALAR} must be recorded in the parent process.`);
+  Assert.equal(Object.keys(keyedScalars[KEYED_BOOL_SCALAR]).length, 1,
+            `${KEYED_BOOL_SCALAR} must only contain the expected key in parent process.`);
+  Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]['parent-key'], false,
+            `${KEYED_BOOL_SCALAR} must have the correct value (parent process).`);
+}
+
+function checkContentScalars(processData) {
+  const scalars = processData.scalars;
+  const keyedScalars = processData.keyedScalars;
+
+  // Check the plain scalars for the content process.
+  Assert.ok(!(UINT_SCALAR in scalars),
+            "Scalars must not be recorded in other processes unless allowed.");
+  Assert.ok(!(KEYED_UINT_SCALAR in keyedScalars),
+            "Keyed scalars must not be recorded in other processes unless allowed.");
+  Assert.ok(CONTENT_ONLY_UINT_SCALAR in scalars,
+            `${CONTENT_ONLY_UINT_SCALAR} must be recorded in the content process.`);
+  Assert.equal(scalars[CONTENT_ONLY_UINT_SCALAR], 37,
+            `${CONTENT_ONLY_UINT_SCALAR} must have the correct value (content process).`);
+  Assert.ok(ALL_CHILD_PROCESSES_STRING_SCALAR in scalars,
+            `${ALL_CHILD_PROCESSES_STRING_SCALAR} must be recorded in the content process.`);
+  Assert.equal(scalars[ALL_CHILD_PROCESSES_STRING_SCALAR], "all-child-processes",
+            `${ALL_CHILD_PROCESSES_STRING_SCALAR} must have the correct value (content process).`);
+  Assert.ok(ALL_PROCESSES_UINT_SCALAR in scalars,
+            `${ALL_PROCESSES_UINT_SCALAR} must be recorded in the content process.`);
+  Assert.equal(scalars[ALL_PROCESSES_UINT_SCALAR], 37,
+            `${ALL_PROCESSES_UINT_SCALAR} must have the correct value (content process).`);
+
+  // Check the keyed scalars.
+  Assert.ok(KEYED_BOOL_SCALAR in keyedScalars,
+            `${KEYED_BOOL_SCALAR} must be recorded in the content process.`);
+  Assert.ok('content-key' in keyedScalars[KEYED_BOOL_SCALAR],
+            `${KEYED_BOOL_SCALAR} must be recorded in the content process.`);
+  Assert.ok('content-key2' in keyedScalars[KEYED_BOOL_SCALAR],
+            `${KEYED_BOOL_SCALAR} must be recorded in the content process.`);
+  Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]['content-key'], true,
+            `${KEYED_BOOL_SCALAR} must have the correct value (content process).`);
+  Assert.equal(keyedScalars[KEYED_BOOL_SCALAR]['content-key2'], false,
+            `${KEYED_BOOL_SCALAR} must have the correct value (content process).`);
+  Assert.equal(Object.keys(keyedScalars[KEYED_BOOL_SCALAR]).length, 2,
+            `${KEYED_BOOL_SCALAR} must contain the expected keys in content process.`);
+}
+
+/**
+ * This function waits until content scalars are reported into the
+ * scalar snapshot.
+ */
+function* waitForContentScalars() {
+  yield ContentTaskUtils.waitForCondition(() => {
+    const scalars =
+      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    return Object.keys(scalars).includes("tab");
+  });
+}
+
+add_task(function*() {
+  if (!runningInParent) {
+    TelemetryController.testSetupContent();
+    run_child_test();
+    do_send_remote_message(MESSAGE_CHILD_TEST_DONE);
+    return;
+  }
+
+  // Setup.
+  do_get_profile(true);
+  loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
+  Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
+  yield TelemetryController.testSetup();
+  if (runningInParent) {
+    setParentScalars();
+    // Make sure we don't generate unexpected pings due to pref changes.
+    yield setEmptyPrefWatchlist();
+  }
+
+  // Run test in child, don't wait for it to finish: just wait for the
+  // MESSAGE_CHILD_TEST_DONE.
+  run_test_in_child("test_ChildScalars.js");
+  yield do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
+
+  // Once scalars are set by the content process, they don't immediately get
+  // sent to the parent process. Wait for the Telemetry IPC Timer to trigger
+  // and batch send the data back to the parent process.
+  yield waitForContentScalars();
+
+  // Get an "environment-changed" ping rather than a "test-ping", as
+  // scalar measurements are only supported in subsession pings.
+  const payload = TelemetrySession.getPayload("environment-change");
+
+  // Validate the scalar data.
+  Assert.ok("processes" in payload, "Should have processes section");
+  Assert.ok("content" in payload.processes, "Should have child process section");
+  Assert.ok("scalars" in payload.processes.content, "Child process section should have scalars.");
+  Assert.ok("keyedScalars" in payload.processes.content, "Child process section should have keyed scalars.");
+  checkParentScalars(payload.processes.parent);
+  checkContentScalars(payload.processes.content);
+
+  do_test_finished();
+});
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js
@@ -2,30 +2,37 @@
    http://creativecommons.org/publicdomain/zero/1.0/
 */
 
 const UINT_SCALAR = "telemetry.test.unsigned_int_kind";
 const STRING_SCALAR = "telemetry.test.string_kind";
 const BOOLEAN_SCALAR = "telemetry.test.boolean_kind";
 const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int";
 
+function getParentProcessScalars(aChannel, aKeyed = false, aClear = false) {
+  const scalars = aKeyed ?
+    Telemetry.snapshotKeyedScalars(aChannel, aClear)["default"] :
+    Telemetry.snapshotScalars(aChannel, aClear)["default"];
+  return scalars || {};
+}
+
 add_task(function* test_serializationFormat() {
   Telemetry.clearScalars();
 
   // Set the scalars to a known value.
   const expectedUint = 3785;
   const expectedString = "some value";
   Telemetry.scalarSet(UINT_SCALAR, expectedUint);
   Telemetry.scalarSet(STRING_SCALAR, expectedString);
   Telemetry.scalarSet(BOOLEAN_SCALAR, true);
   Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "first_key", 1234);
 
-  // Get a snapshot of the scalars.
+  // Get a snapshot of the scalars for the main process (internally called "default").
   const scalars =
-    Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   // Check that they are serialized to the correct format.
   Assert.equal(typeof(scalars[UINT_SCALAR]), "number",
                UINT_SCALAR + " must be serialized to the correct format.");
   Assert.ok(Number.isInteger(scalars[UINT_SCALAR]),
                UINT_SCALAR + " must be a finite integer.");
   Assert.equal(scalars[UINT_SCALAR], expectedUint,
                UINT_SCALAR + " must have the correct value.");
@@ -50,17 +57,17 @@ add_task(function* test_keyedSerializati
   const expectedOtherValue = 1107;
 
   Telemetry.scalarSet(UINT_SCALAR, expectedUint);
   Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, expectedKey, expectedUint);
   Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, expectedOtherKey, expectedOtherValue);
 
   // Get a snapshot of the scalars.
   const keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
 
   Assert.ok(!(UINT_SCALAR in keyedScalars),
             UINT_SCALAR + " must not be serialized with the keyed scalars.");
   Assert.ok(KEYED_UINT_SCALAR in keyedScalars,
             KEYED_UINT_SCALAR + " must be serialized with the keyed scalars.");
   Assert.equal(Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length, 2,
                "The keyed scalar must contain exactly 2 keys.");
   Assert.ok(expectedKey in keyedScalars[KEYED_UINT_SCALAR],
@@ -98,23 +105,22 @@ add_task(function* test_nonexistingScala
   Assert.throws(() => Telemetry.keyedScalarSet(NON_EXISTING_SCALAR, "some_key", 11715),
                 /NS_ERROR_ILLEGAL_VALUE/,
                 "Setting a non existing keyed scalar must throw.");
   Assert.throws(() => Telemetry.keyedScalarSetMaximum(NON_EXISTING_SCALAR, "some_key", 11715),
                 /NS_ERROR_ILLEGAL_VALUE/,
                 "Setting the maximum of a non keyed existing scalar must throw.");
 
   // Get a snapshot of the scalars.
-  const scalars =
-    Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+  const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
 
   Assert.ok(!(NON_EXISTING_SCALAR in scalars), "The non existing scalar must not be persisted.");
 
   const keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
 
   Assert.ok(!(NON_EXISTING_SCALAR in keyedScalars),
             "The non existing keyed scalar must not be persisted.");
 });
 
 add_task(function* test_expiredScalar() {
   const EXPIRED_SCALAR = "telemetry.test.expired";
   const EXPIRED_KEYED_SCALAR = "telemetry.test.keyed_expired";
@@ -132,31 +138,31 @@ add_task(function* test_expiredScalar() 
   Telemetry.keyedScalarSetMaximum(EXPIRED_KEYED_SCALAR, "some_key", 11715);
 
   // The unexpired scalar has an expiration version, but far away in the future.
   const expectedValue = 11716;
   Telemetry.scalarSet(UNEXPIRED_SCALAR, expectedValue);
 
   // Get a snapshot of the scalars.
   const scalars =
-    Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
   const keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
 
   Assert.ok(!(EXPIRED_SCALAR in scalars), "The expired scalar must not be persisted.");
   Assert.equal(scalars[UNEXPIRED_SCALAR], expectedValue,
                "The unexpired scalar must be persisted with the correct value.");
   Assert.ok(!(EXPIRED_KEYED_SCALAR in keyedScalars),
             "The expired keyed scalar must not be persisted.");
 });
 
 add_task(function* test_unsignedIntScalar() {
   let checkScalar = (expectedValue) => {
     const scalars =
-      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     Assert.equal(scalars[UINT_SCALAR], expectedValue,
                  UINT_SCALAR + " must contain the expected value.");
   };
 
   Telemetry.clearScalars();
 
   // Let's start with an accumulation without a prior set.
   Telemetry.scalarAdd(UINT_SCALAR, 1);
@@ -204,17 +210,17 @@ add_task(function* test_unsignedIntScala
                 "Setting the scalar to an unexpected value type must throw.");
   // The stored value must not be compromised.
   checkScalar(1);
 });
 
 add_task(function* test_stringScalar() {
   let checkExpectedString = (expectedString) => {
     const scalars =
-      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     Assert.equal(scalars[STRING_SCALAR], expectedString,
                  STRING_SCALAR + " must contain the expected string value.");
   };
 
   Telemetry.clearScalars();
 
   // Let's check simple strings...
   let expected = "test string";
@@ -245,17 +251,17 @@ add_task(function* test_stringScalar() {
   const LONG_STRING = "browser.qaxfiuosnzmhlg.rpvxicawolhtvmbkpnludhedobxvkjwqyeyvmv";
   Telemetry.scalarSet(STRING_SCALAR, LONG_STRING);
   checkExpectedString(LONG_STRING.substr(0, 50));
 });
 
 add_task(function* test_booleanScalar() {
   let checkExpectedBool = (expectedBoolean) => {
     const scalars =
-      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     Assert.equal(scalars[BOOLEAN_SCALAR], expectedBoolean,
                  BOOLEAN_SCALAR + " must contain the expected boolean value.");
   };
 
   Telemetry.clearScalars();
 
   // Set a test boolean value.
   let expected = false;
@@ -294,24 +300,24 @@ add_task(function* test_booleanScalar() 
 });
 
 add_task(function* test_scalarRecording() {
   const OPTIN_SCALAR = "telemetry.test.release_optin";
   const OPTOUT_SCALAR = "telemetry.test.release_optout";
 
   let checkValue = (scalarName, expectedValue) => {
     const scalars =
-      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     Assert.equal(scalars[scalarName], expectedValue,
                  scalarName + " must contain the expected value.");
   };
 
   let checkNotSerialized = (scalarName) => {
     const scalars =
-      Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
     Assert.ok(!(scalarName in scalars), scalarName + " was not recorded.");
   };
 
   Telemetry.canRecordBase = false;
   Telemetry.canRecordExtended = false;
   Telemetry.clearScalars();
 
   // Check that no scalar is recorded if both base and extended recording are off.
@@ -337,24 +343,24 @@ add_task(function* test_scalarRecording(
 
 add_task(function* test_keyedScalarRecording() {
   const OPTIN_SCALAR = "telemetry.test.keyed_release_optin";
   const OPTOUT_SCALAR = "telemetry.test.keyed_release_optout";
   const testKey = "policy_key";
 
   let checkValue = (scalarName, expectedValue) => {
     const scalars =
-      Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
     Assert.equal(scalars[scalarName][testKey], expectedValue,
                  scalarName + " must contain the expected value.");
   };
 
   let checkNotSerialized = (scalarName) => {
     const scalars =
-      Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+      getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
     Assert.ok(!(scalarName in scalars), scalarName + " was not recorded.");
   };
 
   Telemetry.canRecordBase = false;
   Telemetry.canRecordExtended = false;
   Telemetry.clearScalars();
 
   // Check that no scalar is recorded if both base and extended recording are off.
@@ -384,35 +390,35 @@ add_task(function* test_subsession() {
   // Set the scalars to a known value.
   Telemetry.scalarSet(UINT_SCALAR, 3785);
   Telemetry.scalarSet(STRING_SCALAR, "some value");
   Telemetry.scalarSet(BOOLEAN_SCALAR, false);
   Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "some_random_key", 12);
 
   // Get a snapshot and reset the subsession. The value we set must be there.
   let scalars =
-    Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false, true);
   let keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, true);
 
   Assert.equal(scalars[UINT_SCALAR], 3785,
                UINT_SCALAR + " must contain the expected value.");
   Assert.equal(scalars[STRING_SCALAR], "some value",
                STRING_SCALAR + " must contain the expected value.");
   Assert.equal(scalars[BOOLEAN_SCALAR], false,
                BOOLEAN_SCALAR + " must contain the expected value.");
   Assert.equal(keyedScalars[KEYED_UINT_SCALAR]["some_random_key"], 12,
                KEYED_UINT_SCALAR + " must contain the expected value.");
 
   // Get a new snapshot and reset the subsession again. Since no new value
   // was set, the scalars should not be reported.
   scalars =
-    Telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false, true);
   keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, true);
 
   Assert.ok(!(UINT_SCALAR in scalars), UINT_SCALAR + " must be empty and not reported.");
   Assert.ok(!(STRING_SCALAR in scalars), STRING_SCALAR + " must be empty and not reported.");
   Assert.ok(!(BOOLEAN_SCALAR in scalars), BOOLEAN_SCALAR + " must be empty and not reported.");
   Assert.ok(!(KEYED_UINT_SCALAR in keyedScalars), KEYED_UINT_SCALAR + " must be empty and not reported.");
 });
 
 add_task(function* test_keyed_uint() {
@@ -432,17 +438,17 @@ add_task(function* test_keyed_uint() {
 
   // Use SetMaximum on the third key.
   Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, KEYS[2], 37);
   expectedValues[2] = 37;
 
   // Get a snapshot of the scalars and make sure the keys contain
   // the correct values.
   const keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
 
   for (let k = 0; k < 3; k++) {
     const keyName = KEYS[k];
     Assert.equal(keyedScalars[KEYED_UINT_SCALAR][keyName], expectedValues[k],
                  KEYED_UINT_SCALAR + "." + keyName + " must contain the correct value.");
   }
 
   // Are we still throwing when doing unsupported things on uint keyed scalars?
@@ -462,28 +468,28 @@ add_task(function* test_keyed_boolean() 
 
   // Set the initial values.
   Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, true);
   Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, false);
 
   // Get a snapshot of the scalars and make sure the keys contain
   // the correct values.
   let keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
   Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][first_key], true,
                "The key must contain the expected value.");
   Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][second_key], false,
                "The key must contain the expected value.");
 
   // Now flip the values and make sure we get the expected values back.
   Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, false);
   Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, true);
 
   keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
   Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][first_key], false,
                "The key must contain the expected value.");
   Assert.equal(keyedScalars[KEYED_BOOLEAN_TYPE][second_key], true,
                "The key must contain the expected value.");
 
   // Are we still throwing when doing unsupported things on a boolean keyed scalars?
   // Just test one single unsupported operation, the other are covered in the plain
   // boolean scalar test.
@@ -510,17 +516,17 @@ add_task(function* test_keyed_keys_lengt
                 /NS_ERROR_ILLEGAL_VALUE/,
                 "Using keys longer than 70 characters must throw.");
   Assert.throws(() => Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LONG_KEY_STRING, 10),
                 /NS_ERROR_ILLEGAL_VALUE/,
                 "Using keys longer than 70 characters must throw.");
 
   // Make sure the key with the right length contains the expected value.
   let keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
   Assert.equal(Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length, 1,
                "The keyed scalar must contain exactly 1 key.");
   Assert.ok(NORMAL_KEY in keyedScalars[KEYED_UINT_SCALAR],
             "The keyed scalar must contain the expected key.");
   Assert.equal(keyedScalars[KEYED_UINT_SCALAR][NORMAL_KEY], 1,
                "The key must contain the expected value.");
   Assert.ok(!(LONG_KEY_STRING in keyedScalars[KEYED_UINT_SCALAR]),
             "The data for the long key should not have been recorded.");
@@ -552,17 +558,17 @@ add_task(function* test_keyed_max_keys()
                 "Using more than 100 keys must throw.");
   Assert.throws(() => Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LAST_KEY_NAME, 10),
                 /NS_ERROR_FAILURE/,
                 "Using more than 100 keys must throw.");
 
   // Make sure all the keys except the last one are available and have the correct
   // values.
   let keyedScalars =
-    Telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+    getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
 
   // Check that the keyed scalar only contain the first 100 keys.
   const reportedKeysSet = new Set(Object.keys(keyedScalars[KEYED_UINT_SCALAR]));
   Assert.ok([...keyNamesSet].filter(x => reportedKeysSet.has(x)) &&
             [...reportedKeysSet].filter(x => keyNamesSet.has(x)),
             "The keyed scalar must contain all the 100 keys, and drop the others.");
 
   // Check that all the keys recorded the expected values.
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -50,15 +50,17 @@ tags = addons
 [test_TelemetrySession.js]
 tags = addons
 [test_ThreadHangStats.js]
 run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high
 [test_TelemetrySend.js]
 [test_ChildHistograms.js]
 skip-if = os == "android"
 tags = addons
+[test_ChildScalars.js]
+skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 [test_TelemetryReportingPolicy.js]
 tags = addons
 [test_TelemetryScalars.js]
 [test_TelemetryTimestamps.js]
 skip-if = toolkit == 'android'
 [test_TelemetryCaptureStack.js]
 [test_TelemetryEvents.js]
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -301,16 +301,20 @@ var PingPicker = {
     }, false);
 
     document.getElementById("newer-ping")
             .addEventListener("click", () => this._movePingIndex(-1), false);
     document.getElementById("older-ping")
             .addEventListener("click", () => this._movePingIndex(1), false);
     document.getElementById("choose-payload")
             .addEventListener("change", () => displayPingData(gPingData), false);
+    document.getElementById("scalars-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
+    document.getElementById("keyed-scalars-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
     document.getElementById("histograms-processes")
             .addEventListener("change", () => displayPingData(gPingData), false);
     document.getElementById("keyed-histograms-processes")
             .addEventListener("change", () => displayPingData(gPingData), false);
   },
 
   onPingSourceChanged() {
     this.update();
@@ -1642,23 +1646,28 @@ var Scalars = {
   /**
    * Render the scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
     let scalarsSection = document.getElementById("scalars");
     removeAllChildNodes(scalarsSection);
 
-    if (!aPayload.processes || !aPayload.processes.parent) {
+    let processesSelect = document.getElementById("scalars-processes");
+    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
+
+    if (!aPayload.processes ||
+        !selectedProcess ||
+        !(selectedProcess in aPayload.processes)) {
       return;
     }
 
-    let scalars = aPayload.processes.parent.scalars;
+    let scalars = aPayload.processes[selectedProcess].scalars;
     const hasData = scalars && Object.keys(scalars).length > 0;
-    setHasData("scalars-section", hasData);
+    setHasData("scalars-section", hasData || processesSelect.options.length);
     if (!hasData) {
       return;
     }
 
     const headingName = bundle.GetStringFromName("namesHeader");
     const headingValue = bundle.GetStringFromName("valuesHeader");
     const table = KeyValueTable.render(scalars, headingName, headingValue);
     scalarsSection.appendChild(table);
@@ -1669,23 +1678,28 @@ var KeyedScalars = {
   /**
    * Render the keyed scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
     let scalarsSection = document.getElementById("keyed-scalars");
     removeAllChildNodes(scalarsSection);
 
-    if (!aPayload.processes || !aPayload.processes.parent) {
+    let processesSelect = document.getElementById("keyed-scalars-processes");
+    let selectedProcess = processesSelect.selectedOptions.item(0).getAttribute("value");
+
+    if (!aPayload.processes ||
+        !selectedProcess ||
+        !(selectedProcess in aPayload.processes)) {
       return;
     }
 
-    let keyedScalars = aPayload.processes.parent.keyedScalars;
+    let keyedScalars = aPayload.processes[selectedProcess].keyedScalars;
     const hasData = keyedScalars && Object.keys(keyedScalars).length > 0;
-    setHasData("keyed-scalars-section", hasData);
+    setHasData("keyed-scalars-section", hasData || processesSelect.options.length);
     if (!hasData) {
       return;
     }
 
     const headingName = bundle.GetStringFromName("namesHeader");
     const headingValue = bundle.GetStringFromName("valuesHeader");
     for (let scalar in keyedScalars) {
       // Add the name of the scalar.
@@ -1960,29 +1974,29 @@ function sortStartupMilestones(aSimpleMe
 
   return result;
 }
 
 function renderProcessList(ping, selectEl) {
   removeAllChildNodes(selectEl);
   let option = document.createElement("option");
   option.appendChild(document.createTextNode("parent"));
-  option.setAttribute("value", "");
+  option.setAttribute("value", "parent");
   option.selected = true;
   selectEl.appendChild(option);
 
   if (!("processes" in ping.payload)) {
     selectEl.disabled = true;
     return;
   }
   selectEl.disabled = false;
 
   for (let process of Object.keys(ping.payload.processes)) {
     // TODO: parent hgrams are on root payload, not in payload.processes.parent
-    // When/If that gets moved, you'll need to remove this:
+    // When/If that gets moved, you'll need to remove this
     if (process === "parent") {
       continue;
     }
     option = document.createElement("option");
     option.appendChild(document.createTextNode(process));
     option.setAttribute("value", process);
     selectEl.appendChild(option);
   }
@@ -2054,16 +2068,18 @@ function displayPingData(ping, updatePay
 
   // Update the structured data rendering.
   const keysHeader = bundle.GetStringFromName("keysHeader");
   const valuesHeader = bundle.GetStringFromName("valuesHeader");
 
   // Update the payload list and process lists
   if (updatePayloadList) {
     renderPayloadList(ping);
+    renderProcessList(ping, document.getElementById("scalars-processes"));
+    renderProcessList(ping, document.getElementById("keyed-scalars-processes"));
     renderProcessList(ping, document.getElementById("histograms-processes"));
     renderProcessList(ping, document.getElementById("keyed-histograms-processes"));
   }
 
   // Show general data.
   GeneralData.render(ping);
 
   // Show environment data.
@@ -2140,16 +2156,20 @@ function displayPingData(ping, updatePay
   let hgramDiv = document.getElementById("histograms");
   removeAllChildNodes(hgramDiv);
 
   let histograms = payload.histograms;
 
   let hgramsSelect = document.getElementById("histograms-processes");
   let hgramsOption = hgramsSelect.selectedOptions.item(0);
   let hgramsProcess = hgramsOption.getAttribute("value");
+  // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
+  if (hgramsProcess === "parent") {
+    hgramsProcess = "";
+  }
   if (hgramsProcess &&
       "processes" in ping.payload &&
       hgramsProcess in ping.payload.processes) {
     histograms = ping.payload.processes[hgramsProcess].histograms;
   }
 
   hasData = Object.keys(histograms).length > 0;
   setHasData("histograms-section", hasData || hgramsSelect.options.length);
@@ -2172,16 +2192,20 @@ function displayPingData(ping, updatePay
   let keyedDiv = document.getElementById("keyed-histograms");
   removeAllChildNodes(keyedDiv);
 
   let keyedHistograms = payload.keyedHistograms;
 
   let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
   let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
   let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
+  // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
+  if (keyedHgramsProcess === "parent") {
+    keyedHgramsProcess = "";
+  }
   if (keyedHgramsProcess &&
       "processes" in ping.payload &&
       keyedHgramsProcess in ping.payload.processes) {
     keyedHistograms = ping.payload.processes[keyedHgramsProcess].keyedHistograms;
   }
 
   setHasData("keyed-histograms-section", keyedHgramsSelect.options.length);
   if (keyedHistograms) {
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -135,25 +135,31 @@
         </div>
       </section>
 
       <section id="scalars-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.scalarsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
+        <div class="processes-ui">
+          <select id="scalars-processes" class="process-picker"></select>
+        </div>
         <div id="scalars" class="data">
         </div>
       </section>
 
       <section id="keyed-scalars-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.keyedScalarsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
+        <div class="processes-ui">
+          <select id="keyed-scalars-processes" class="process-picker"></select>
+        </div>
         <div id="keyed-scalars" class="data">
         </div>
       </section>
 
       <section id="histograms-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.histogramsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -27,17 +27,16 @@ const SCRIPTS = [
   "browser/base/content/browser.js",
   "browser/components/downloads/content/downloads.js",
   "browser/components/downloads/content/indicator.js",
   "browser/components/customizableui/content/panelUI.js",
   "toolkit/components/viewsource/content/viewSourceUtils.js",
   "browser/base/content/browser-addons.js",
   "browser/base/content/browser-ctrlTab.js",
   "browser/base/content/browser-customization.js",
-  "browser/base/content/browser-devedition.js",
   "browser/base/content/browser-feeds.js",
   "browser/base/content/browser-fullScreenAndPointerLock.js",
   "browser/base/content/browser-fullZoom.js",
   "browser/base/content/browser-gestureSupport.js",
   "browser/base/content/browser-media.js",
   "browser/base/content/browser-places.js",
   "browser/base/content/browser-plugins.js",
   "browser/base/content/browser-refreshblocker.js",