merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 20 Sep 2016 12:01:29 +0200
changeset 314470 62f79d676e0e11b3ad59a5425b3ebb3ec5bbefb5
parent 314434 e81f8c1e38fbcd3763bf0bb87f17133ddf36d8b4 (current diff)
parent 314469 8192ae07b2ba5c67d170d9d5a4257aabd52dae2d (diff)
child 314498 76bd16c9b4059c1788358725e86e277bca0eea98
child 314548 14705f779a46da3dbbc41af098209911479cff01
child 314615 150109898e5fe28f6f1e3b2587fc357329f5195f
push id30729
push usercbook@mozilla.com
push dateTue, 20 Sep 2016 10:01:39 +0000
treeherdermozilla-central@62f79d676e0e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
first release with
nightly linux32
62f79d676e0e / 52.0a1 / 20160920030429 / files
nightly linux64
62f79d676e0e / 52.0a1 / 20160920030429 / files
nightly mac
62f79d676e0e / 52.0a1 / 20160920030429 / files
nightly win32
62f79d676e0e / 52.0a1 / 20160920030429 / files
nightly win64
62f79d676e0e / 52.0a1 / 20160920030429 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
taskcluster/ci/nightly-fennec/build.yml
taskcluster/ci/nightly-fennec/docker_build.yml
taskcluster/ci/nightly-fennec/mobile_base.yml
taskcluster/ci/nightly-fennec/routes.json
taskcluster/ci/signing/kind.yml
taskcluster/ci/signing/signing.yml
taskcluster/taskgraph/task/nightly_fennec.py
--- a/accessible/ipc/DocAccessibleChildBase.cpp
+++ b/accessible/ipc/DocAccessibleChildBase.cpp
@@ -77,23 +77,61 @@ DocAccessibleChildBase::SerializeTree(Ac
   aTree.AppendElement(AccessibleData(id, role, childCount, interfaces));
 #endif
 
   for (uint32_t i = 0; i < childCount; i++) {
     SerializeTree(aRoot->GetChildAt(i), aTree);
   }
 }
 
+#if defined(XP_WIN)
+/* static */ void
+DocAccessibleChildBase::SetMsaaIds(Accessible* aRoot,
+                                   uint32_t& aMsaaIdIndex,
+                                   const nsTArray<MsaaMapping>& aNewMsaaIds)
+{
+  const MsaaMapping& mapping = aNewMsaaIds[aMsaaIdIndex];
+#if defined(DEBUG)
+  uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
+  MOZ_ASSERT(mapping.ID() == id);
+#endif // defined(DEBUG)
+  static_cast<AccessibleWrap*>(aRoot)->SetID(mapping.MsaaID());
+  ++aMsaaIdIndex;
+  if (aRoot->IsOuterDoc()) {
+    // This needs to match the tree traversal in SerializeTree
+    return;
+  }
+  for (uint32_t i = 0, n = aRoot->ChildCount(); i < n; ++i) {
+    SetMsaaIds(aRoot->GetChildAt(i), aMsaaIdIndex, aNewMsaaIds);
+  }
+}
+#endif // defined(XP_WIN)
+
 void
 DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->InsertionIndex();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
   SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
+#if defined(XP_WIN)
+  nsTArray<MsaaMapping> newMsaaIds;
+  SendShowEventInfo(data, &newMsaaIds);
+  // newMsaaIds could be empty if something went wrong in SendShowEvent()
+  if (!newMsaaIds.IsEmpty()) {
+    uint32_t index = 0;
+    SetMsaaIds(aShowEvent->GetAccessible(), index, newMsaaIds);
+  }
+  // NB: On Windows, SendShowEvent attaches the subtree and generates new IDs,
+  //     but does *NOT* fire the native event. We need to do that after
+  //     we've called SetMsaaIds.
+  SendEvent(reinterpret_cast<uint64_t>(aShowEvent->GetAccessible()->UniqueID()),
+            nsIAccessibleEvent::EVENT_SHOW);
+#else
   SendShowEvent(data, aShowEvent->IsFromUserInput());
+#endif // defined(XP_WIN)
 }
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/ipc/DocAccessibleChildBase.h
+++ b/accessible/ipc/DocAccessibleChildBase.h
@@ -55,16 +55,20 @@ public:
 
     mDoc->SetIPCDoc(nullptr);
     mDoc = nullptr;
   }
 
 protected:
   static uint32_t InterfacesFor(Accessible* aAcc);
   static void SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree);
+#if defined(XP_WIN)
+  static void SetMsaaIds(Accessible* aRoot, uint32_t& aMsaaIdIndex,
+                         const nsTArray<MsaaMapping>& aNewMsaaIds);
+#endif
 
   DocAccessible*  mDoc;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_DocAccessibleChildBase_h
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -11,18 +11,23 @@
 #include "xpcAccEvents.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 
 namespace mozilla {
 namespace a11y {
 
 bool
+#if defined(XP_WIN)
+DocAccessibleParent::RecvShowEventInfo(const ShowEventData& aData,
+                                       nsTArray<MsaaMapping>* aNewMsaaIds)
+#else
 DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
                                    const bool& aFromUser)
+#endif // defined(XP_WIN)
 {
   if (mShutdown)
     return true;
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
   if (aData.NewTree().IsEmpty()) {
     NS_ERROR("no children being added");
@@ -39,17 +44,23 @@ DocAccessibleParent::RecvShowEvent(const
   }
 
   uint32_t newChildIdx = aData.Idx();
   if (newChildIdx > parent->ChildrenCount()) {
     NS_ERROR("invalid index to add child at");
     return true;
   }
 
+#if defined(XP_WIN)
+  aNewMsaaIds->SetCapacity(aData.NewTree().Length());
+  uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx,
+                                 aNewMsaaIds);
+#else
   uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
+#endif
   MOZ_ASSERT(consumed == aData.NewTree().Length());
 
   // XXX This shouldn't happen, but if we failed to add children then the below
   // is pointless and can crash.
   if (!consumed) {
     return true;
   }
 
@@ -57,38 +68,46 @@ DocAccessibleParent::RecvShowEvent(const
   for (uint32_t i = 0; i < consumed; i++) {
     uint64_t id = aData.NewTree()[i].ID();
     MOZ_ASSERT(mAccessibles.GetEntry(id));
   }
 #endif
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
+  // NB: On Windows we dispatch the native event via a subsequent call to
+  // RecvEvent().
+#if !defined(XP_WIN)
   ProxyAccessible* target = parent->ChildAt(newChildIdx);
   ProxyShowHideEvent(target, parent, true, aFromUser);
 
   if (!nsCoreUtils::AccEventObserversExist()) {
     return true;
   }
 
   uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
   xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
   xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
   nsIDOMNode* node = nullptr;
   RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node,
                                               aFromUser);
   nsCoreUtils::DispatchAccEvent(Move(event));
+#endif
 
   return true;
 }
 
 uint32_t
 DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
                                 const nsTArray<a11y::AccessibleData>& aNewTree,
-                                uint32_t aIdx, uint32_t aIdxInParent)
+                                uint32_t aIdx, uint32_t aIdxInParent
+#if defined(XP_WIN)
+                                , nsTArray<MsaaMapping>* aNewMsaaIds
+#endif
+                                )
 {
   if (aNewTree.Length() <= aIdx) {
     NS_ERROR("bad index in serialized tree!");
     return 0;
   }
 
   const AccessibleData& newChild = aNewTree[aIdx];
   if (newChild.Role() > roles::LAST_ROLE) {
@@ -119,20 +138,32 @@ DocAccessibleParent::AddSubtree(ProxyAcc
     new ProxyAccessible(newChild.ID(), aParent, this, role,
                         newChild.Interfaces());
 #endif
 
   aParent->AddChildAt(aIdxInParent, newProxy);
   mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
   ProxyCreated(newProxy, newChild.Interfaces());
 
+#if defined(XP_WIN)
+  Accessible* idForAcc = WrapperFor(newProxy);
+  MOZ_ASSERT(idForAcc);
+  uint32_t newMsaaId = AccessibleWrap::GetChildIDFor(idForAcc);
+  MOZ_ASSERT(newMsaaId);
+  aNewMsaaIds->AppendElement(MsaaMapping(newChild.ID(), newMsaaId));
+#endif // defined(XP_WIN)
+
   uint32_t accessibles = 1;
   uint32_t kids = newChild.ChildrenCount();
   for (uint32_t i = 0; i < kids; i++) {
-    uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
+    uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i
+#if defined(XP_WIN)
+                                   , aNewMsaaIds
+#endif
+                                  );
     if (!consumed)
       return 0;
 
     accessibles += consumed;
   }
 
   MOZ_ASSERT(newProxy->ChildrenCount() == kids);
 
@@ -471,26 +502,29 @@ DocAccessibleParent::GetXPCAccessible(Pr
 /**
  * @param aCOMProxy COM Proxy to the document in the content process.
  * @param aParentCOMProxy COM Proxy to the OuterDocAccessible that is
  *        the parent of the document. The content process will use this
  *        proxy when traversing up across the content/chrome boundary.
  */
 bool
 DocAccessibleParent::RecvCOMProxy(const IAccessibleHolder& aCOMProxy,
-                                  IAccessibleHolder* aParentCOMProxy)
+                                  IAccessibleHolder* aParentCOMProxy,
+                                  uint32_t* aMsaaID)
 {
   RefPtr<IAccessible> ptr(aCOMProxy.Get());
   SetCOMInterface(ptr);
 
   Accessible* outerDoc = OuterDocOfRemoteBrowser();
   IAccessible* rawNative = nullptr;
   if (outerDoc) {
     outerDoc->GetNativeInterface((void**) &rawNative);
   }
 
   aParentCOMProxy->Set(IAccessibleHolder::COMPtrType(rawNative));
+  Accessible* wrapper = WrapperFor(this);
+  *aMsaaID = AccessibleWrap::GetChildIDFor(wrapper);
   return true;
 }
 #endif // defined(XP_WIN)
 
 } // a11y
 } // mozilla
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -45,18 +45,23 @@ public:
 
   /*
    * Called when a message from a document in a child process notifies the main
    * process it is firing an event.
    */
   virtual bool RecvEvent(const uint64_t& aID, const uint32_t& aType)
     override;
 
+#if defined(XP_WIN)
+  virtual bool RecvShowEventInfo(const ShowEventData& aData,
+                                 nsTArray<MsaaMapping>* aNewMsaaIds) override;
+#else
   virtual bool RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
     override;
+#endif // defined(XP_WIN)
   virtual bool RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser)
     override;
   virtual bool RecvStateChangeEvent(const uint64_t& aID,
                                     const uint64_t& aState,
                                     const bool& aEnabled) override final;
 
   virtual bool RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
     override final;
@@ -139,17 +144,18 @@ public:
     { return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID); }
 
   size_t ChildDocCount() const { return mChildDocs.Length(); }
   const DocAccessibleParent* ChildDocAt(size_t aIdx) const
     { return mChildDocs[aIdx]; }
 
 #if defined(XP_WIN)
   virtual bool RecvCOMProxy(const IAccessibleHolder& aCOMProxy,
-                            IAccessibleHolder* aParentCOMProxy) override;
+                            IAccessibleHolder* aParentCOMProxy,
+                            uint32_t* aMsaaID) override;
 #endif
 
 private:
 
   class ProxyEntry : public PLDHashEntryHdr
   {
   public:
     explicit ProxyEntry(const void*) : mProxy(nullptr) {}
@@ -169,17 +175,21 @@ private:
 
     enum { ALLOW_MEMMOVE = true };
 
     ProxyAccessible* mProxy;
   };
 
   uint32_t AddSubtree(ProxyAccessible* aParent,
                       const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
-                      uint32_t aIdxInParent);
+                      uint32_t aIdxInParent
+#if defined(XP_WIN)
+                      , nsTArray<MsaaMapping>* aNewMsaaIds
+#endif // defined(XP_WIN)
+                      );
   MOZ_MUST_USE bool CheckDocTree() const;
   xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
   nsTArray<DocAccessibleParent*> mChildDocs;
   DocAccessibleParent* mParentDoc;
 
   /*
    * Conceptually this is a map from IDs to proxies, but we store the ID in the
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -29,15 +29,17 @@ DocAccessibleChild::~DocAccessibleChild(
 {
   MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
 }
 
 void
 DocAccessibleChild::SendCOMProxy(const IAccessibleHolder& aProxy)
 {
   IAccessibleHolder parentProxy;
-  PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy);
+  uint32_t msaaID = AccessibleWrap::kNoID;
+  PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy, &msaaID);
   mParentProxy.reset(parentProxy.Release());
+  mDoc->SetID(msaaID);
 }
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/ipc/win/PDocAccessible.ipdl
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -22,16 +22,22 @@ struct AccessibleData
 
 struct ShowEventData
 {
   uint64_t ID;
   uint32_t Idx;
   AccessibleData[] NewTree;
 };
 
+struct MsaaMapping
+{
+  uint64_t ID;
+  uint32_t MsaaID;
+};
+
 struct Attribute
 {
   nsCString Name;
   nsString Value;
 };
 
 sync protocol PDocAccessible
 {
@@ -40,17 +46,17 @@ sync protocol PDocAccessible
 parent:
   async Shutdown();
 
   /*
    * Notify the parent process the document in the child process is firing an
    * event.
    */
   async Event(uint64_t aID, uint32_t type);
-  async ShowEvent(ShowEventData data, bool aFromuser);
+  sync ShowEventInfo(ShowEventData data) returns (MsaaMapping[] aNewMsaaIds);
   async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
   async CaretMoveEvent(uint64_t aID, int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
   async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
   async RoleChangedEvent(uint32_t aRole);
 
@@ -58,16 +64,16 @@ parent:
    * Tell the parent document to bind the existing document as a new child
    * document.
    */
   async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
 
   // For now we'll add the command to send the proxy here. This might move to
   // PDocAccessible constructor in PBrowser.
   sync COMProxy(IAccessibleHolder aDocCOMProxy)
-    returns(IAccessibleHolder aParentCOMProxy);
+    returns(IAccessibleHolder aParentCOMProxy, uint32_t aMsaaID);
 
 child:
   async __delete__();
 };
 
 }
 }
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -4,16 +4,18 @@
  * 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 "AccessibleWrap.h"
 #include "Accessible-inl.h"
 
 #include "Compatibility.h"
 #include "DocAccessible-inl.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/a11y/DocAccessibleChild.h"
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "EnumVariant.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleEvent.h"
 #include "nsWinUtils.h"
 #include "mozilla/a11y/ProxyAccessible.h"
 #include "ProxyWrappers.h"
@@ -64,46 +66,42 @@ IDSet AccessibleWrap::sIDGen;
 
 static const int32_t kIEnumVariantDisconnected = -1;
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccessibleWrap
 ////////////////////////////////////////////////////////////////////////////////
 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
   Accessible(aContent, aDoc)
-#ifdef _WIN64
   , mID(kNoID)
-#endif
 {
 }
 
 AccessibleWrap::~AccessibleWrap()
 {
 #ifdef _WIN64
-  if (mID != kNoID)
+  if (mID != kNoID && XRE_IsParentProcess())
     sIDGen.ReleaseID(mID);
 #endif
 }
 
 ITypeInfo* AccessibleWrap::gTypeInfo = nullptr;
 
 NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible)
 
 void
 AccessibleWrap::Shutdown()
 {
-#ifdef _WIN64
   if (mID != kNoID) {
     auto doc = static_cast<DocAccessibleWrap*>(mDoc);
     MOZ_ASSERT(doc);
     if (doc) {
       doc->RemoveID(mID);
     }
   }
-#endif
 
   Accessible::Shutdown();
 }
 
 //-----------------------------------------------------
 // IUnknown interface methods - see iunknown.h for documentation
 //-----------------------------------------------------
 
@@ -1143,18 +1141,31 @@ AccessibleWrap::Invoke(DISPID dispIdMemb
 void
 AccessibleWrap::GetNativeInterface(void** aOutAccessible)
 {
   *aOutAccessible = static_cast<IAccessible*>(this);
   NS_ADDREF_THIS();
 }
 
 void
+AccessibleWrap::SetID(uint32_t aID)
+{
+  MOZ_ASSERT(XRE_IsContentProcess());
+  mID = aID;
+  DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document());
+  DebugOnly<AccessibleWrap*> checkAcc = nullptr;
+  MOZ_ASSERT(!(checkAcc = doc->GetAccessibleByID(aID)) ||
+             checkAcc->GetExistingID() == aID);
+  doc->AddID(aID, this);
+}
+
+void
 AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
   static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY,
                 "MSAA event map skewed");
 
   NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), "invalid event type");
 
   uint32_t winEvent = gWinEventMap[aEventType];
   if (!winEvent)
     return;
@@ -1241,16 +1252,23 @@ AccessibleWrap::GetChildIDFor(Accessible
   // A child ID of the window is required, when we use NotifyWinEvent,
   // so that the 3rd party application can call back and get the IAccessible
   // the event occurred on.
 
   if (!aAccessible) {
     return 0;
   }
 
+  // Content should use mID which has been generated by the chrome process.
+  if (XRE_IsContentProcess() && !aAccessible->IsApplication()) {
+    const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID;
+    MOZ_ASSERT(id != kNoID);
+    return id;
+  }
+
 #ifdef _WIN64
   if (!aAccessible->Document() && !aAccessible->IsProxy())
     return 0;
 
   uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID;
   if (*id != kNoID)
     return *id;
 
@@ -1281,16 +1299,32 @@ AccessibleWrap::GetChildIDFor(Accessible
 
 HWND
 AccessibleWrap::GetHWNDFor(Accessible* aAccessible)
 {
   if (!aAccessible) {
     return nullptr;
   }
 
+  if (XRE_IsContentProcess()) {
+    DocAccessible* doc = aAccessible->Document();
+    if (!doc) {
+      return nullptr;
+    }
+
+    DocAccessibleChild* ipcDoc = doc->IPCDoc();
+    if (!ipcDoc) {
+      return nullptr;
+    }
+
+    auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager());
+    MOZ_ASSERT(tab);
+    return reinterpret_cast<HWND>(tab->GetNativeWindowHandle());
+  }
+
   // Accessibles in child processes are said to have the HWND of the window
   // their tab is within.  Popups are always in the parent process, and so
   // never proxied, which means this is basically correct.
   if (aAccessible->IsProxy()) {
     ProxyAccessible* proxy = aAccessible->Proxy();
     if (!proxy) {
       return nullptr;
     }
@@ -1340,34 +1374,32 @@ AccessibleWrap::NativeAccessible(Accessi
     return nullptr;
   }
 
   IAccessible* msaaAccessible = nullptr;
   aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible));
   return static_cast<IDispatch*>(msaaAccessible);
 }
 
-#ifdef _WIN64
 static Accessible*
 GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID)
 {
   Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID);
   if (child)
     return child;
 
   uint32_t childDocCount = aDoc->ChildDocumentCount();
   for (uint32_t i = 0; i < childDocCount; i++) {
     child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID);
     if (child)
       return child;
   }
 
     return nullptr;
   }
-#endif
 
 static AccessibleWrap*
 GetProxiedAccessibleInSubtree(const DocAccessibleParent* aDoc, uint32_t aID)
 {
   auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc));
   AccessibleWrap* child = wrapper->GetAccessibleByID(aID);
   if (child) {
     return child;
@@ -1421,17 +1453,19 @@ AccessibleWrap::GetXPAccessibleFor(const
   if (!IsProxy()) {
     void* uniqueID = reinterpret_cast<void*>(intptr_t(-aVarChild.lVal));
 
     DocAccessible* document = Document();
     Accessible* child =
 #ifdef _WIN64
       GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal));
 #else
-      document->GetAccessibleByUniqueIDInSubtree(uniqueID);
+      XRE_IsContentProcess() ?
+        GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal)) :
+        document->GetAccessibleByUniqueIDInSubtree(uniqueID);
 #endif
 
     // If it is a document then just return an accessible.
     if (child && IsDoc())
       return child;
 
     // Otherwise check whether the accessible is a child (this path works for
     // ARIA documents and popups).
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -174,27 +174,25 @@ public: // construction, destruction
    * Find an accessible by the given child ID in cached documents.
    */
   Accessible* GetXPAccessibleFor(const VARIANT& aVarChild);
 
   virtual void GetNativeInterface(void **aOutAccessible) override;
 
   static IDispatch* NativeAccessible(Accessible* aAccessible);
 
-#ifdef _WIN64
   uint32_t GetExistingID() const { return mID; }
   static const uint32_t kNoID = 0;
-#endif
+  // This is only valid to call in content
+  void SetID(uint32_t aID);
 
 protected:
   virtual ~AccessibleWrap();
 
-#ifdef _WIN64
   uint32_t mID;
-#endif
 
   /**
    * Return the wrapper for the document's proxy.
    */
   DocProxyAccessibleWrap* DocProxyWrapper() const;
 
   /**
    * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -43,25 +43,20 @@ IMPL_IUNKNOWN_QUERY_HEAD(DocAccessibleWr
     return S_OK;
   }
 IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(HyperTextAccessibleWrap)
 
 STDMETHODIMP
 DocAccessibleWrap::get_accParent(
       /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
 {
-  HRESULT hr = DocAccessible::get_accParent(ppdispParent);
-  if (*ppdispParent) {
-    return hr;
-  }
-
   // We might be a top-level document in a content process.
   DocAccessibleChild* ipcDoc = IPCDoc();
   if (!ipcDoc) {
-    return S_FALSE;
+    return DocAccessible::get_accParent(ppdispParent);
   }
   IAccessible* dispParent = ipcDoc->GetParentIAccessible();
   if (!dispParent) {
     return S_FALSE;
   }
 
   dispParent->AddRef();
   *ppdispParent = static_cast<IDispatch*>(dispParent);
--- a/accessible/windows/msaa/DocAccessibleWrap.h
+++ b/accessible/windows/msaa/DocAccessibleWrap.h
@@ -35,35 +35,31 @@ public:
   virtual void Shutdown();
 
   // DocAccessible
   virtual void* GetNativeWindow() const;
 
   /**
    * Manage the mapping from id to Accessible.
    */
-#ifdef _WIN64
   void AddID(uint32_t aID, AccessibleWrap* aAcc)
     { mIDToAccessibleMap.Put(aID, aAcc); }
   void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
   AccessibleWrap* GetAccessibleByID(uint32_t aID) const
     { return mIDToAccessibleMap.Get(aID); }
-#endif
 
 protected:
   // DocAccessible
   virtual void DoInitialUpdate();
 
 protected:
   void* mHWND;
 
   /*
    * This provides a mapping from 32 bit id to accessible objects.
    */
-#ifdef _WIN64
   nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-#endif
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -1,21 +1,27 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 
 requestLongerTimeout(2);
 
 var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Services.jsm");
 
 const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
 
 /**
  * Enable local telemetry recording for the duration of the tests.
  */
 var gOldContentCanRecord = false;
+var gOldParentCanRecord = false;
 add_task(function* test_initialize() {
+  let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  gOldParentCanRecord = Telemetry.canRecordExtended
+  Telemetry.canRecordExtended = true;
+
   // Because canRecordExtended is a per-process variable, we need to make sure
   // that all of the pages load in the same content process. Limit the number
   // of content processes to at most 1 (or 0 if e10s is off entirely).
   yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] });
 
   gOldContentCanRecord = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
     let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
     let old = telemetry.canRecordExtended;
@@ -69,16 +75,19 @@ add_task(function* () {
   // document counters, because we won't be re-parsing the SVG documents.
   yield check_use_counter_iframe("file_use_counter_svg_background.html",
                                  "PROPERTY_FILL", false);
   yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html",
                                  "PROPERTY_FILL", false);
 });
 
 add_task(function* () {
+  let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  Telemetry.canRecordExtended = gOldParentCanRecord;
+
   yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
       telemetry.canRecordExtended = arg.oldCanRecord;
       resolve();
     });
   });
@@ -99,46 +108,42 @@ function waitForPageLoad(browser) {
         removeEventListener("load", listener, true);
         resolve();
       }
       addEventListener("load", listener, true);
     });
   });
 }
 
-function grabHistogramsFromContent(browser, use_counter_middlefix) {
-  return ContentTask.spawn(browser, { middlefix: use_counter_middlefix }, function* (arg) {
-    let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
-    function snapshot_histogram(name) {
-      return telemetry.getHistogramById(name).snapshot();
-    }
-
-    let histogram_page_name = "USE_COUNTER2_" + arg.middlefix + "_PAGE";
-    let histogram_document_name = "USE_COUNTER2_" + arg.middlefix + "_DOCUMENT";
-    let histogram_page = snapshot_histogram(histogram_page_name);
-    let histogram_document = snapshot_histogram(histogram_document_name);
-    let histogram_docs = snapshot_histogram("CONTENT_DOCUMENTS_DESTROYED");
-    let histogram_toplevel_docs = snapshot_histogram("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED");
-    return [histogram_page.sum, histogram_document.sum,
-            histogram_docs.sum, histogram_toplevel_docs.sum];
-  });
+function grabHistogramsFromContent(use_counter_middlefix, page_before = null) {
+  let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+  let suffix = Services.appinfo.browserTabsRemoteAutostart ? "#content" : "";
+  let gather = () => [
+    telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum,
+    telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_DOCUMENT" + suffix).snapshot().sum,
+    telemetry.getHistogramById("CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+    telemetry.getHistogramById("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+  ];
+  return BrowserTestUtils.waitForCondition(() => {
+    return page_before != telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum;
+  }).then(gather, gather);
 }
 
 var check_use_counter_iframe = Task.async(function* (file, use_counter_middlefix, check_documents=true) {
   info("checking " + file + " with histogram " + use_counter_middlefix);
 
   let newTab = gBrowser.addTab( "about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
   yield waitForPageLoad(gBrowser.selectedBrowser);
 
   // Inject our desired file into the iframe of the newly-loaded page.
   yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     let deferred = PromiseUtils.defer();
@@ -146,17 +151,17 @@ var check_use_counter_iframe = Task.asyn
     let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
 
     let iframe = content.document.getElementById('content');
     iframe.src = opts.file;
     let listener = (event) => {
       event.target.removeEventListener("load", listener, true);
 
       // We flush the main document first, then the iframe's document to
-      // ensure any propagation that might happen from child->parent should
+      // ensure any propagation that might happen from content->parent should
       // have already happened when counters are reported to telemetry.
       wu.forceUseCounterFlush(content.document);
       wu.forceUseCounterFlush(iframe.contentDocument);
 
       deferred.resolve();
     };
     iframe.addEventListener("load", listener, true);
 
@@ -169,17 +174,17 @@ var check_use_counter_iframe = Task.asyn
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
 
   is(histogram_page_after, histogram_page_before + 1,
      "page counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   if (check_documents) {
     is(histogram_document_after, histogram_document_before + 1,
        "document counts for " + use_counter_middlefix + " after are correct");
@@ -192,17 +197,17 @@ var check_use_counter_img = Task.async(f
   let newTab = gBrowser.addTab("about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
   yield waitForPageLoad(gBrowser.selectedBrowser);
 
   // Inject our desired file into the img of the newly-loaded page.
   yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     let deferred = PromiseUtils.defer();
@@ -234,17 +239,17 @@ var check_use_counter_img = Task.async(f
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
   is(histogram_page_after, histogram_page_before + 1,
      "page counts for " + use_counter_middlefix + " after are correct");
   is(histogram_document_after, histogram_document_before + 1,
      "document counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   // 2 documents: one for the outer html page containing the <img> element, and
   // one for the SVG image itself.
@@ -258,17 +263,17 @@ var check_use_counter_direct = Task.asyn
   let newTab = gBrowser.addTab( "about:blank");
   gBrowser.selectedTab = newTab;
   newTab.linkedBrowser.stop();
 
   // Hold on to the current values of the telemetry histograms we're
   // interested in.
   let [histogram_page_before, histogram_document_before,
        histogram_docs_before, histogram_toplevel_docs_before] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix);
 
   gBrowser.selectedBrowser.loadURI(gHttpTestRoot + file);
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let listener = () => {
         removeEventListener("load", listener, true);
 
@@ -287,17 +292,17 @@ var check_use_counter_direct = Task.asyn
   // The histograms only get recorded when the document actually gets
   // destroyed, which might not have happened yet due to GC/CC effects, etc.
   // Try to force document destruction.
   yield waitForDestroyedDocuments();
 
   // Grab histograms again and compare.
   let [histogram_page_after, histogram_document_after,
        histogram_docs_after, histogram_toplevel_docs_after] =
-      yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix);
+      yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
   (xfail ? todo_is : is)(histogram_page_after, histogram_page_before + 1,
                          "page counts for " + use_counter_middlefix + " after are correct");
   (xfail ? todo_is : is)(histogram_document_after, histogram_document_before + 1,
                          "document counts for " + use_counter_middlefix + " after are correct");
   ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
      "top level document counts are correct");
   ok(histogram_docs_after >= histogram_docs_before + 1,
      "document counts are correct");
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5408,8 +5408,24 @@ ContentParent::SendGetFilesResponseAndFo
 void
 ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
 {
   if (!mHangMonitorActor) {
     return;
   }
   ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
 }
+
+bool
+ContentParent::RecvAccumulateChildHistogram(
+                InfallibleTArray<Accumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChild(aAccumulations);
+  return true;
+}
+
+bool
+ContentParent::RecvAccumulateChildKeyedHistogram(
+                InfallibleTArray<KeyedAccumulation>&& aAccumulations)
+{
+  Telemetry::AccumulateChildKeyed(aAccumulations);
+  return true;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1132,16 +1132,20 @@ private:
   virtual bool RecvNotifyLowMemory() override;
 
   virtual bool RecvGetFilesRequest(const nsID& aID,
                                    const nsString& aDirectoryPath,
                                    const bool& aRecursiveFlag) override;
 
   virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override;
 
+  virtual bool RecvAccumulateChildHistogram(
+                  InfallibleTArray<Accumulation>&& aAccumulations) override;
+  virtual bool RecvAccumulateChildKeyedHistogram(
+                  InfallibleTArray<KeyedAccumulation>&& aAccumulations) 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/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -832,16 +832,24 @@ child:
     /**
      * Tell the child to print the current page with the given settings.
      *
      * @param aOuterWindowID the ID of the outer window to print
      * @param aPrintData the serialized settings to print with
      */
     async Print(uint64_t aOuterWindowID, PrintData aPrintData);
 
+    /**
+     * Update the child with the tab's current top-level native window handle.
+     * This is used by a11y objects who must expose their native window.
+     *
+     * @param aNewHandle The native window handle of the tab's top-level window.
+     */
+    async UpdateNativeWindowHandle(uintptr_t aNewHandle);
+
 /*
  * FIXME: write protocol!
 
 state LIVE:
     send LoadURL goto LIVE;
 //etc.
     send Destroy goto DYING;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -95,16 +95,18 @@ using mozilla::dom::ContentParentId from
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 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::DocShellOriginAttributes 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";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -1211,16 +1213,22 @@ parent:
      async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag);
      async DeleteGetFilesRequest(nsID aID);
 
      async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob,
                                                 Principal principal);
 
      async UnstoreAndBroadcastBlobURLUnregistration(nsCString url);
 
+    /**
+     * Messages for communicating child Telemetry to the parent process
+     */
+    async AccumulateChildHistogram(Accumulation[] accumulations);
+    async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
+
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -544,16 +544,19 @@ TabChild::TabChild(nsIContentChild* aMan
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(true)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mAPZChild(nullptr)
   , mLayerObserverEpoch(0)
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  , mNativeWindowHandle(0)
+#endif
 {
   // In the general case having the TabParent tell us if APZ is enabled or not
   // doesn't really work because the TabParent itself may not have a reference
   // to the owning widget during initialization. Instead we assume that this
   // TabChild corresponds to a widget type that would have APZ enabled, and just
   // check the other conditions necessary for enabling APZ.
   mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled();
 
@@ -2530,16 +2533,27 @@ TabChild::RecvPrint(const uint64_t& aOut
     return true;
   }
 
 #endif
   return true;
 }
 
 bool
+TabChild::RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle)
+{
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  mNativeWindowHandle = aNewHandle;
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool
 TabChild::RecvDestroy()
 {
   MOZ_ASSERT(mDestroyed == false);
   mDestroyed = true;
 
   nsTArray<PContentPermissionRequestChild*> childArray =
       nsContentPermissionUtils::GetContentPermissionRequestChildById(GetTabId());
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -585,16 +585,18 @@ public:
 
   virtual bool RecvHandledWindowedPluginKeyEvent(
                  const mozilla::NativeEventData& aKeyEventData,
                  const bool& aIsConsumed) override;
 
   virtual bool RecvPrint(const uint64_t& aOuterWindowID,
                          const PrintData& aPrintData) override;
 
+  virtual bool RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle) override;
+
   /**
    * Native widget remoting protocol for use with windowed plugins with e10s.
    */
   PPluginWidgetChild* AllocPPluginWidgetChild() override;
 
   bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) override;
 
   nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut);
@@ -646,16 +648,20 @@ public:
   void SetAPZChild(layers::APZChild* aAPZChild)
   {
       mAPZChild = aAPZChild;
   }
 
   // Request that the docshell be marked as active.
   void ForcePaint(uint64_t aLayerObserverEpoch);
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
+#endif
+
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual bool RecvDestroy() override;
@@ -784,15 +790,20 @@ private:
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
   // APZChild clears this pointer from its destructor, so it shouldn't be a
   // dangling pointer.
   layers::APZChild* mAPZChild;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   uint64_t mLayerObserverEpoch;
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  // The handle associated with the native window that contains this tab
+  uintptr_t mNativeWindowHandle;
+#endif // defined(XP_WIN)
+
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -395,16 +395,27 @@ TabParent::SetOwnerElement(Element* aEle
   }
 
   if (mFrameElement) {
     bool useGlobalHistory =
       !mFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disableglobalhistory);
     Unused << SendSetUseGlobalHistory(useGlobalHistory);
   }
 
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  if (!mIsDestroyed) {
+    uintptr_t newWindowHandle = 0;
+    if (nsCOMPtr<nsIWidget> widget = GetWidget()) {
+      newWindowHandle =
+        reinterpret_cast<uintptr_t>(widget->GetNativeData(NS_NATIVE_WINDOW));
+    }
+    Unused << SendUpdateNativeWindowHandle(newWindowHandle);
+  }
+#endif
+
   AddWindowListeners();
   TryCacheDPIAndScale();
 }
 
 void
 TabParent::AddWindowListeners()
 {
   if (mFrameElement && mFrameElement->OwnerDoc()) {
--- a/gfx/tests/reftest/reftest.list
+++ b/gfx/tests/reftest/reftest.list
@@ -1,9 +1,9 @@
 # 468496-1 will also detect bugs in video drivers.
 == 468496-1.html 468496-1-ref.html
-fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
+fuzzy(175,443) == 611498-1.html 611498-ref.html
 skip-if(B2G) fuzzy-if(Android,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
 skip-if(!asyncPan) == 1086723.html 1086723-ref.html
 == 853889-1.html 853889-1-ref.html
 skip-if(Android) fuzzy-if(skiaContent,1,587) == 1143303-1.svg pass.svg
 fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
 == 1131264-1.svg pass.svg
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -533,16 +533,20 @@ private:
 
   // These affect how line scrolls from wheel events will be accelerated.
   DECL_GFX_PREF(Live, "mousewheel.acceleration.factor",        MouseWheelAccelerationFactor, int32_t, -1);
   DECL_GFX_PREF(Live, "mousewheel.acceleration.start",         MouseWheelAccelerationStart, int32_t, -1);
 
   // This affects whether events will be routed through APZ or not.
   DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.enabled",
                                                                MouseWheelHasRootScrollDeltaOverride, bool, false);
+  DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.horizontal.factor",
+                                                               MouseWheelRootScrollHorizontalFactor, int32_t, 0);
+  DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.vertical.factor",
+                                                               MouseWheelRootScrollVerticalFactor, int32_t, 0);
   DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100);
   DECL_GFX_PREF(Live, "mousewheel.transaction.timeout",        MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);
 
   DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
 
   DECL_GFX_PREF(Live, "test.events.async.enabled",             TestEventsAsyncEnabled, bool, false);
   DECL_GFX_PREF(Live, "test.mousescroll",                      MouseScrollTestingEnabled, bool, false);
 
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -483,16 +483,17 @@ MessageChannel::MessageChannel(MessageLi
     mDispatchingAsyncMessagePriority(0),
     mTransactionStack(nullptr),
     mTimedOutMessageSeqno(0),
     mTimedOutMessagePriority(0),
     mRemoteStackDepthGuess(false),
     mSawInterruptOutMsg(false),
     mIsWaitingForIncoming(false),
     mAbortOnError(false),
+    mNotifiedChannelDone(false),
     mFlags(REQUIRE_DEFAULT),
     mPeerPidSet(false),
     mPeerPid(-1)
 #if defined(MOZ_CRASHREPORTER) && defined(OS_WIN)
     , mPending(AnnotateAllocator<Message>(*this))
 #endif
 {
     MOZ_COUNT_CTOR(ipc::MessageChannel);
@@ -2076,16 +2077,23 @@ MessageChannel::NotifyMaybeChannelError(
         return;
     }
 
     Clear();
 
     // Oops, error!  Let the listener know about it.
     mChannelState = ChannelError;
 
+    // IPDL assumes these notifications do not fire twice, so we do not let
+    // that happen.
+    if (mNotifiedChannelDone) {
+      return;
+    }
+    mNotifiedChannelDone = true;
+
     // After this, the channel may be deleted.  Based on the premise that
     // mListener owns this channel, any calls back to this class that may
     // work with mListener should still work on living objects.
     mListener->OnChannelError();
 }
 
 void
 MessageChannel::OnNotifyMaybeChannelError()
@@ -2233,16 +2241,23 @@ MessageChannel::NotifyChannelClosed()
 {
     mMonitor->AssertNotCurrentThreadOwns();
 
     if (ChannelClosed != mChannelState)
         NS_RUNTIMEABORT("channel should have been closed!");
 
     Clear();
 
+    // IPDL assumes these notifications do not fire twice, so we do not let
+    // that happen.
+    if (mNotifiedChannelDone) {
+      return;
+    }
+    mNotifiedChannelDone = true;
+
     // OK, the IO thread just closed the channel normally.  Let the
     // listener know about it. After this point the channel may be
     // deleted.
     mListener->OnChannelClose();
 }
 
 void
 MessageChannel::DebugAbort(const char* file, int line, const char* cond,
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -778,16 +778,20 @@ class MessageChannel : HasResultCodes
 #ifdef OS_WIN
     HANDLE mEvent;
 #endif
 
     // Should the channel abort the process from the I/O thread when
     // a channel error occurs?
     bool mAbortOnError;
 
+    // True if the listener has already been notified of a channel close or
+    // error.
+    bool mNotifiedChannelDone;
+
     // See SetChannelFlags
     ChannelFlags mFlags;
 
     // Task and state used to asynchronously notify channel has been connected
     // safely.  This is necessary to be able to cancel notification if we are
     // closed at the same time.
     RefPtr<RefCountedTask> mOnChannelConnectedTask;
     bool mPeerPidSet;
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -504,16 +504,19 @@ public:
                           mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) {
             return false;
         }
         mValid = false;
         aActor->SetTransport(Move(t));
         return true;
     }
 
+    bool IsValid() const {
+        return mValid;
+    }
 
 private:
     friend struct IPC::ParamTraits<Endpoint<PFooSide>>;
 
     Endpoint(const Endpoint&) = delete;
     Endpoint& operator=(const Endpoint&) = delete;
 
     bool mValid;
@@ -644,17 +647,20 @@ struct ParamTraits<mozilla::ipc::ActorHa
 
 template<class PFooSide>
 struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>>
 {
     typedef mozilla::ipc::Endpoint<PFooSide> paramType;
 
     static void Write(Message* aMsg, const paramType& aParam)
     {
-        MOZ_RELEASE_ASSERT(aParam.mValid);
+        IPC::WriteParam(aMsg, aParam.mValid);
+        if (!aParam.mValid) {
+            return;
+        }
 
         IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mMode));
 
         // We duplicate the descriptor so that our own file descriptor remains
         // valid after the write. An alternative would be to set
         // aParam.mTransport.mValid to false, but that won't work because aParam
         // is const.
         mozilla::ipc::TransportDescriptor desc = mozilla::ipc::DuplicateDescriptor(aParam.mTransport);
@@ -663,17 +669,25 @@ struct ParamTraits<mozilla::ipc::Endpoin
         IPC::WriteParam(aMsg, aParam.mMyPid);
         IPC::WriteParam(aMsg, aParam.mOtherPid);
         IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mProtocolId));
     }
 
     static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
     {
         MOZ_RELEASE_ASSERT(!aResult->mValid);
-        aResult->mValid = true;
+
+        if (!IPC::ReadParam(aMsg, aIter, &aResult->mValid)) {
+            return false;
+        }
+        if (!aResult->mValid) {
+            // Object is empty, but read succeeded.
+            return true;
+        }
+
         uint32_t mode, protocolId;
         if (!IPC::ReadParam(aMsg, aIter, &mode) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) ||
             !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid) ||
             !IPC::ReadParam(aMsg, aIter, &protocolId)) {
             return false;
         }
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -2379,17 +2379,20 @@ def _generateCxxUnion(ud):
         StmtExpr(callAssertSanity(uvar=othervar)),
         copyswitch,
         StmtExpr(ExprAssn(mtypevar, othertype))
     ])
     cls.addstmts([ copyctor, Whitespace.NL ])
 
     # ~Union()
     dtor = DestructorDefn(DestructorDecl(ud.name))
-    dtor.addstmt(StmtExpr(callMaybeDestroy(tnonevar)))
+    # The void cast prevents Coverity from complaining about missing return
+    # value checks.
+    dtor.addstmt(StmtExpr(ExprCast(callMaybeDestroy(tnonevar), Type.VOID,
+                                   static=1)))
     cls.addstmts([ dtor, Whitespace.NL ])
 
     # type()
     typemeth = MethodDefn(MethodDecl('type', ret=typetype,
                                      const=1, force_inline=1))
     typemeth.addstmt(StmtReturn(mtypevar))
     cls.addstmts([ typemeth, Whitespace.NL ])
 
@@ -2420,19 +2423,24 @@ def _generateCxxUnion(ud):
         case = StmtBlock()
         case.addstmts([
             maybeReconstruct(c, rhstypevar),
             StmtExpr(c.callOperatorEq(
                 ExprCall(ExprSelect(rhsvar, '.', c.getConstTypeName())))),
             StmtBreak()
         ])
         opeqswitch.addcase(CaseLabel(c.enum()), case)
-    opeqswitch.addcase(CaseLabel(tnonevar.name),
-                       StmtBlock([ StmtExpr(callMaybeDestroy(rhstypevar)),
-                                   StmtBreak() ]))
+    opeqswitch.addcase(
+        CaseLabel(tnonevar.name),
+        # The void cast prevents Coverity from complaining about missing return
+        # value checks.
+        StmtBlock([ StmtExpr(ExprCast(callMaybeDestroy(rhstypevar), Type.VOID,
+                                      static=1)),
+                    StmtBreak() ])
+    )
     opeqswitch.addcase(
         DefaultLabel(),
         StmtBlock([ _logicError('unreached'), StmtBreak() ]))
     opeq.addstmts([
         StmtExpr(callAssertSanity(uvar=rhsvar)),
         StmtDecl(Decl(typetype, rhstypevar.name), init=ud.callType(rhsvar)),
         opeqswitch,
         StmtExpr(ExprAssn(mtypevar, rhstypevar)),
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6069,38 +6069,44 @@ CodeGenerator::visitCreateArgumentsObjec
     Register callObj = ToRegister(lir->getCallObject());
     Register temp = ToRegister(lir->temp0());
     Label done;
 
     if (ArgumentsObject* templateObj = lir->mir()->templateObject()) {
         Register objTemp = ToRegister(lir->temp1());
         Register cxTemp = ToRegister(lir->temp2());
 
+        masm.Push(callObj);
+
         // Try to allocate an arguments object. This will leave the reserved
         // slots uninitialized, so it's important we don't GC until we
         // initialize these slots in ArgumentsObject::finishForIon.
         Label failure;
         masm.createGCObject(objTemp, temp, templateObj, gc::DefaultHeap, &failure,
                             /* initContents = */ false);
 
         masm.moveStackPtrTo(temp);
-        masm.addPtr(Imm32(frameSize()), temp);
+        masm.addPtr(Imm32(masm.framePushed()), temp);
 
         masm.setupUnalignedABICall(cxTemp);
         masm.loadJSContext(cxTemp);
         masm.passABIArg(cxTemp);
         masm.passABIArg(temp);
         masm.passABIArg(callObj);
         masm.passABIArg(objTemp);
 
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ArgumentsObject::finishForIon));
-        masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, masm.exceptionLabel());
+        masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, &failure);
+
+        // Discard saved callObj on the stack.
+        masm.addToStackPtr(Imm32(sizeof(uintptr_t)));
         masm.jump(&done);
 
         masm.bind(&failure);
+        masm.Pop(callObj);
     }
 
     masm.moveStackPtrTo(temp);
     masm.addPtr(Imm32(frameSize()), temp);
 
     pushArg(callObj);
     pushArg(temp);
     callVM(NewIonArgumentsObjectInfo, lir);
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -378,17 +378,19 @@ ArgumentsObject::finishForIon(JSContext*
     unsigned numActuals = frame->numActualArgs();
     unsigned numFormals = callee->nargs();
     unsigned numArgs = Max(numActuals, numFormals);
     unsigned numBytes = ArgumentsData::bytesRequired(numArgs);
 
     ArgumentsData* data =
         reinterpret_cast<ArgumentsData*>(AllocateObjectBuffer<uint8_t>(cx, obj, numBytes));
     if (!data) {
-        // Make the object safe for GC.
+        // Make the object safe for GC. Don't report OOM, the slow path will
+        // retry the allocation.
+        cx->recoverFromOutOfMemory();
         obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr));
         return nullptr;
     }
 
     data->numArgs = numArgs;
     data->rareData = nullptr;
 
     obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT));
--- a/taskcluster/ci/spidermonkey/kind.yml
+++ b/taskcluster/ci/spidermonkey/kind.yml
@@ -44,27 +44,30 @@ jobs:
             using: spidermonkey-package
             spidermonkey-variant: plain
         run-on-projects:
             - integration
             - release
         when:
             files-changed:
                 - build/**
+                - config/**
                 - configure.py
                 - dom/bindings/**
                 - intl/icu/**
                 - js/moz.configure
                 - layout/tools/reftest/reftest/**
+                - Makefile.in
                 - media/webrtc/trunk/tools/gyp/**
                 - memory/**
                 - mfbt/**
                 - modules/fdlibm/**
                 - modules/zlib/src/**
                 - mozglue/**
+                - moz.build
                 - moz.configure
                 - nsprpub/**
                 - python/**
                 - taskcluster/moz.build
                 - testing/mozbase/**
                 - test.mozbuild
                 - toolkit/mozapps/installer/package-name.mk
                 - toolkit/mozapps/installer/upload-files.mk
--- a/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini
+++ b/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini
@@ -1,3 +1,4 @@
 [iframe-allow-fullscreen.html.ini]
   type: testharness
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1282024
   prefs: [full-screen-api.unprefix.enabled:true]
--- a/testing/xpcshell/selftest.py
+++ b/testing/xpcshell/selftest.py
@@ -9,30 +9,30 @@ import mozunit
 import os
 import pprint
 import re
 import shutil
 import sys
 import tempfile
 import unittest
 
+from buildconfig import substs
 from StringIO import StringIO
 from mozlog import structured
 from mozbuild.base import MozbuildObject
 os.environ.pop('MOZ_OBJDIR', None)
 build_obj = MozbuildObject.from_environment()
 
 from runxpcshelltests import XPCShellTests
 
 mozinfo.find_and_update_from_json()
 
 objdir = build_obj.topobjdir.encode("utf-8")
 
 if mozinfo.isMac:
-  from buildconfig import substs
   xpcshellBin = os.path.join(objdir, "dist", substs['MOZ_MACBUNDLE_NAME'], "Contents", "MacOS", "xpcshell")
 else:
   xpcshellBin = os.path.join(objdir, "dist", "bin", "xpcshell")
   if sys.platform == "win32":
     xpcshellBin += ".exe"
 
 TEST_PASS_STRING = "TEST-PASS"
 TEST_FAIL_STRING = "TEST-UNEXPECTED-FAIL"
@@ -853,17 +853,19 @@ add_test({
         """
         Ensure a simple test with an uncaught rejection is reported.
         """
         self.writeFile("test_simple_uncaught_rejection.js", SIMPLE_UNCAUGHT_REJECTION_TEST)
         self.writeManifest(["test_simple_uncaught_rejection.js"])
 
         self.assertTestResult(False)
         self.assertInLog(TEST_FAIL_STRING)
-        self.assertInLog("test_simple_uncaught_rejection.js:3:3")
+        if not substs.get('RELEASE_BUILD'):
+          # async stacks are currently not enabled in release builds.
+          self.assertInLog("test_simple_uncaught_rejection.js:3:3")
         self.assertInLog("Test rejection.")
         self.assertEquals(1, self.x.testCount)
         self.assertEquals(0, self.x.passCount)
         self.assertEquals(1, self.x.failCount)
 
     def testUncaughtRejectionJSM(self):
         """
         Ensure a simple test with an uncaught rejection from Promise.jsm is reported.
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2343,16 +2343,23 @@ TelemetryImpl::SnapshotScalars(unsigned 
 
 NS_IMETHODIMP
 TelemetryImpl::ClearScalars()
 {
   TelemetryScalar::ClearScalars();
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TelemetryImpl::FlushBatchedChildTelemetry()
+{
+  TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+  return NS_OK;
+}
+
 size_t
 TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   size_t n = aMallocSizeOf(this);
 
   // Ignore the hashtables in mAddonMap; they are not significant.
   n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
   n += TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf);
@@ -2789,16 +2796,28 @@ AccumulateCategorical(ID id, const nsCSt
 void
 AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
 {
   Accumulate(aHistogram,
              static_cast<uint32_t>((end - start).ToMilliseconds()));
 }
 
 void
+AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChild(aAccumulations);
+}
+
+void
+AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  TelemetryHistogram::AccumulateChildKeyed(aAccumulations);
+}
+
+void
 ClearHistogram(ID aId)
 {
   TelemetryHistogram::ClearHistogram(aId);
 }
 
 const char*
 GetHistogramName(ID id)
 {
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -28,16 +28,19 @@
  *****************************************************************************/
 
 namespace mozilla {
 namespace HangMonitor {
   class HangAnnotations;
 } // namespace HangMonitor
 namespace Telemetry {
 
+struct Accumulation;
+struct KeyedAccumulation;
+
 enum TimerResolution {
   Millisecond,
   Microsecond
 };
 
 /**
  * Create and destroy the underlying base::StatisticsRecorder singleton.
  * Creation has to be done very early in the startup sequence.
@@ -121,16 +124,30 @@ void AccumulateCategorical(ID id, const 
  *
  * @param id - histogram id
  * @param start - start time
  * @param end - end time
  */
 void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now());
 
 /**
+ * Accumulate child data into child histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChild(const nsTArray<Accumulation>& aAccumulations);
+
+/**
+ * Accumulate child data into child keyed histograms
+ *
+ * @param aAccumulations - accumulation actions to perform
+ */
+void AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations);
+
+/**
  * This clears the data for a histogram in TelemetryHistogramEnums.h.
  *
  * @param id - histogram id
  */
 void ClearHistogram(ID id);
 
 /**
  * Enable/disable recording for this histogram at runtime.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryComms.h
@@ -0,0 +1,84 @@
+/* -*-  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
+ */
+
+#ifndef Telemetry_Comms_h__
+#define Telemetry_Comms_h__
+
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+enum ID : uint32_t;
+
+struct Accumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+};
+
+struct KeyedAccumulation
+{
+  mozilla::Telemetry::ID mId;
+  uint32_t mSample;
+  nsCString mKey;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+namespace IPC {
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::Accumulation>
+{
+  typedef mozilla::Telemetry::Accumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template<>
+struct
+ParamTraits<mozilla::Telemetry::KeyedAccumulation>
+{
+  typedef mozilla::Telemetry::KeyedAccumulation paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aMsg->WriteUInt32(aParam.mId);
+    WriteParam(aMsg, aParam.mSample);
+    WriteParam(aMsg, aParam.mKey);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) ||
+        !ReadParam(aMsg, aIter, &(aResult->mSample)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mKey))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+} // namespace IPC
+
+#endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -9,33 +9,40 @@
 #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/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 "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;
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // Naming: there are two kinds of functions in this file:
 //
 // * Functions named internal_*: these can only be reached via an
@@ -87,16 +94,17 @@ using mozilla::StaticMutexAutoLock;
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE TYPES
 
 #define EXPIRED_ID "__expired__"
 #define SUBSESSION_HISTOGRAM_PREFIX "sub#"
 #define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
+#define CHILD_HISTOGRAM_SUFFIX "#content"
 
 namespace {
 
 using mozilla::Telemetry::Common::AutoHashtable;
 using mozilla::Telemetry::Common::IsExpiredVersion;
 using mozilla::Telemetry::Common::CanRecordDataset;
 using mozilla::Telemetry::Common::IsInDataset;
 
@@ -180,16 +188,23 @@ 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
 
@@ -199,16 +214,27 @@ 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
 
@@ -306,16 +332,26 @@ HistogramInfo::label_id(const char* labe
       *labelId = i;
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
+bool
+StringEndsWith(const std::string& name, const std::string& suffix)
+{
+  if (name.size() < suffix.size()) {
+    return false;
+  }
+
+  return name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0;
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: Histogram Get, Add, Clone, Clear functions
 
@@ -390,49 +426,70 @@ internal_HistogramGet(const char *name, 
     break;
   default:
     NS_ASSERTION(false, "Invalid histogram type");
     return NS_ERROR_INVALID_ARG;
   }
   return NS_OK;
 }
 
+CharPtrEntryType*
+internal_GetHistogramMapEntry(const char* name)
+{
+  nsDependentCString histogramName(name);
+  NS_NAMED_LITERAL_CSTRING(suffix, CHILD_HISTOGRAM_SUFFIX);
+  if (!StringEndsWith(histogramName, suffix)) {
+    return gHistogramMap.GetEntry(name);
+  }
+  auto root = Substring(histogramName, 0, histogramName.Length() - suffix.Length());
+  return gHistogramMap.GetEntry(PromiseFlatCString(root).get());
+}
+
 nsresult
 internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id)
 {
   if (!gInitDone) {
     return NS_ERROR_FAILURE;
   }
 
-  CharPtrEntryType *entry = gHistogramMap.GetEntry(name);
+  CharPtrEntryType *entry = internal_GetHistogramMapEntry(name);
   if (!entry) {
     return NS_ERROR_INVALID_ARG;
   }
   *id = entry->mData;
   return NS_OK;
 }
 
 // O(1) histogram lookup by numeric id
 nsresult
-internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret)
+internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret,
+                              bool child = false)
 {
   static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
-  Histogram *h = knownHistograms[id];
+  static Histogram* knownChildHistograms[mozilla::Telemetry::HistogramCount] = {0};
+  Histogram *h = child ? knownChildHistograms[id] : knownHistograms[id];
   if (h) {
     *ret = h;
     return NS_OK;
   }
 
   const HistogramInfo &p = gHistograms[id];
   if (p.keyed) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = internal_HistogramGet(p.id(), p.expiration(), p.histogramType,
-                                      p.min, p.max, p.bucketCount, true, &h);
+  nsCString histogramName;
+  histogramName.Append(p.id());
+  if (child) {
+    histogramName.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  }
+
+  nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
+                                      p.histogramType, p.min, p.max,
+                                      p.bucketCount, true, &h);
   if (NS_FAILED(rv))
     return rv;
 
 #ifdef DEBUG
   // Check that the C++ Histogram code computes the same ranges as the
   // Python histogram code.
   if (!IsExpiredVersion(p.expiration())) {
     const struct bounds &b = gBucketLowerBoundIndex[id];
@@ -442,31 +499,37 @@ internal_GetHistogramByEnumId(mozilla::T
       for (int i = 0; i < b.length; ++i) {
         MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
                    "C++/Python bucket mismatch");
       }
     }
   }
 #endif
 
-  *ret = knownHistograms[id] = h;
+  if (child) {
+    *ret = knownChildHistograms[id] = h;
+  } else {
+    *ret = knownHistograms[id] = h;
+  }
   return NS_OK;
 }
 
 nsresult
 internal_GetHistogramByName(const nsACString &name, Histogram **ret)
 {
   mozilla::Telemetry::ID id;
   nsresult rv
     = internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  rv = internal_GetHistogramByEnumId(id, ret);
+  bool isChild = StringEndsWith(name,
+                                NS_LITERAL_CSTRING(CHILD_HISTOGRAM_SUFFIX));
+  rv = internal_GetHistogramByEnumId(id, ret, isChild);
   if (NS_FAILED(rv))
     return rv;
 
   return NS_OK;
 }
 
 /**
  * This clones a histogram |existing| with the id |existingId| to a
@@ -512,42 +575,53 @@ internal_CloneHistogram(const nsACString
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   return internal_CloneHistogram(newName, existingId, *existing);
 }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+
 Histogram*
 internal_GetSubsessionHistogram(Histogram& existing)
 {
   mozilla::Telemetry::ID id;
   nsresult rv
     = internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
   if (NS_FAILED(rv) || gHistograms[id].keyed) {
     return nullptr;
   }
 
+  bool isChild = StringEndsWith(existing.histogram_name(),
+                                CHILD_HISTOGRAM_SUFFIX);
+
   static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
-  if (subsession[id]) {
-    return subsession[id];
+  static Histogram* subsessionChild[mozilla::Telemetry::HistogramCount] = {};
+  Histogram* cached = isChild ? subsessionChild[id] : subsession[id];
+  if (cached) {
+    return cached;
   }
 
   NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
   nsDependentCString existingName(gHistograms[id].id());
   if (StringBeginsWith(existingName, prefix)) {
     return nullptr;
   }
 
   nsCString subsessionName(prefix);
-  subsessionName.Append(existingName);
+  subsessionName.Append(existing.histogram_name().c_str());
 
-  subsession[id] = internal_CloneHistogram(subsessionName, id, existing);
-  return subsession[id];
+  Histogram* clone = internal_CloneHistogram(subsessionName, id, existing);
+  if (isChild) {
+    subsessionChild[id] = clone;
+  } else {
+    subsession[id] = clone;
+  }
+  return clone;
 }
 #endif
 
 nsresult
 internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
 {
   // Check if we are allowed to record the data.
   bool canRecordDataset = CanRecordDataset(dataset,
@@ -586,36 +660,23 @@ internal_HistogramAdd(Histogram& histogr
       return NS_OK;
     }
     dataset = gHistograms[id].dataset;
   }
 
   return internal_HistogramAdd(histogram, value, dataset);
 }
 
-nsresult
-internal_HistogramAddCategorical(mozilla::Telemetry::ID id, const nsCString& label)
-{
-  uint32_t labelId = 0;
-  if (NS_FAILED(gHistograms[id].label_id(label.get(), &labelId))) {
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
-
-  Histogram* h = nullptr;
-  nsresult rv = internal_GetHistogramByEnumId(id, &h);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  return internal_HistogramAdd(*h, labelId);
-}
-
 void
 internal_HistogramClear(Histogram& aHistogram, bool onlySubsession)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
   if (!onlySubsession) {
     aHistogram.Clear();
   }
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) {
     subsession->Clear();
   }
@@ -801,16 +862,18 @@ public:
                          bool subsession, bool clearSubsession);
 
   void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; };
   bool IsRecordingEnabled() const { return mRecordingEnabled; };
 
   nsresult Add(const nsCString& key, uint32_t aSample);
   void Clear(bool subsession);
 
+  nsresult GetEnumId(mozilla::Telemetry::ID& id);
+
 private:
   typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
   typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
   KeyedHistogramMapType mHistogramMap;
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   KeyedHistogramMapType mSubsessionMap;
 #endif
 
@@ -943,16 +1006,20 @@ KeyedHistogram::Add(const nsCString& key
   subsession->Add(sample);
 #endif
   return NS_OK;
 }
 
 void
 KeyedHistogram::Clear(bool onlySubsession)
 {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
   for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
     iter.Get()->mData->Clear();
   }
   mSubsessionMap.Clear();
   if (onlySubsession) {
     return;
   }
@@ -1030,16 +1097,22 @@ KeyedHistogram::GetJSSnapshot(JSContext*
   if (subsession && clearSubsession) {
     Clear(true);
   }
 #endif
 
   return NS_OK;
 }
 
+nsresult
+KeyedHistogram::GetEnumId(mozilla::Telemetry::ID& id)
+{
+  return internal_GetHistogramEnumId(mName.get(), &id);
+}
+
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
 // PRIVATE: KeyedHistogram helpers
 
@@ -1058,16 +1131,329 @@ internal_GetKeyedHistogramById(const nsA
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
+// PRIVATE: functions related to addon histograms
+
+namespace {
+
+// Compute the name to pass into Histogram for the addon histogram
+// 'name' from the addon 'id'.  We can't use 'name' directly because it
+// might conflict with other histograms in other addons or even with our
+// own.
+void
+internal_AddonHistogramName(const nsACString &id, const nsACString &name,
+                            nsACString &ret)
+{
+  ret.Append(id);
+  ret.Append(':');
+  ret.Append(name);
+}
+
+bool
+internal_CreateHistogramForAddon(const nsACString &name,
+                                 AddonHistogramInfo &info)
+{
+  Histogram *h;
+  nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never",
+                                      info.histogramType, info.min, info.max,
+                                      info.bucketCount, true, &h);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  // Don't let this histogram be reported via the normal means
+  // (e.g. Telemetry.registeredHistograms); we'll make it available in
+  // other ways.
+  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
+  info.h = h;
+  return true;
+}
+
+bool
+internal_AddonHistogramReflector(AddonHistogramEntryType *entry,
+                                 JSContext *cx, JS::Handle<JSObject*> obj)
+{
+  AddonHistogramInfo &info = entry->mData;
+
+  // Never even accessed the histogram.
+  if (!info.h) {
+    // Have to force creation of HISTOGRAM_FLAG histograms.
+    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
+      return true;
+
+    if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) {
+      return false;
+    }
+  }
+
+  if (internal_IsEmpty(info.h)) {
+    return true;
+  }
+
+  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
+  if (!snapshot) {
+    // Just consider this to be skippable.
+    return true;
+  }
+  switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) {
+  case REFLECT_FAILURE:
+  case REFLECT_CORRUPT:
+    return false;
+  case REFLECT_OK:
+    const nsACString &histogramName = entry->GetKey();
+    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
+                           snapshot, JSPROP_ENUMERATE)) {
+      return false;
+    }
+    break;
+  }
+  return true;
+}
+
+bool
+internal_AddonReflector(AddonEntryType *entry, JSContext *cx,
+                        JS::Handle<JSObject*> obj)
+{
+  const nsACString &addonId = entry->GetKey();
+  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
+  if (!subobj) {
+    return false;
+  }
+
+  AddonHistogramMapType *map = entry->mData;
+  if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj)
+        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
+                             subobj, JSPROP_ENUMERATE))) {
+    return false;
+  }
+  return true;
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: thread-unsafe helpers for the external interface
+
+// This is a StaticMutex rather than a plain Mutex (1) so that
+// 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 gTelemetryHistogramMutex;
+
+namespace {
+
+void
+internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
+{
+  if (!internal_IsHistogramEnumId(aID)) {
+    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
+    return;
+  }
+
+  if (gHistograms[aID].keyed) {
+    const nsDependentCString id(gHistograms[aID].id());
+    KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
+    if (keyed) {
+      keyed->SetRecordingEnabled(aEnabled);
+      return;
+    }
+  } else {
+    Histogram *h;
+    nsresult rv = internal_GetHistogramByEnumId(aID, &h);
+    if (NS_SUCCEEDED(rv)) {
+      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 {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+      internal_armIPCTimerMainThread();
+    }));
+  }
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gAccumulations) {
+    gAccumulations = new nsTArray<Accumulation>();
+  }
+  if (gAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gAccumulations->AppendElement(Accumulation{aId, aSample});
+  internal_armIPCTimer();
+  return true;
+}
+
+bool
+internal_RemoteAccumulate(mozilla::Telemetry::ID aId,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    return false;
+  }
+  if (!gKeyedAccumulations) {
+    gKeyedAccumulations = new nsTArray<KeyedAccumulation>();
+  }
+  if (gKeyedAccumulations->Length() == kAccumulationsArrayHighWaterMark) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+      TelemetryHistogram::IPCTimerFired(nullptr, nullptr);
+    }));
+  }
+  gKeyedAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey});
+  internal_armIPCTimer();
+  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 ||
+      internal_RemoteAccumulate(aHistogram, aSample)) {
+    return;
+  }
+  Histogram *h;
+  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
+  }
+}
+
+void
+internal_Accumulate(mozilla::Telemetry::ID aID,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  bool isValid = internal_IsHistogramEnumId(aID);
+  MOZ_ASSERT(isValid, "Child keyed telemetry accumulation using invalid id");
+  if (!gInitDone || !internal_CanRecordBase() || !isValid ||
+      internal_RemoteAccumulate(aID, aKey, aSample)) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aID];
+  KeyedHistogram* keyed
+     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+void
+internal_Accumulate(Histogram& aHistogram, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    internal_HistogramAdd(aHistogram, aSample);
+    return;
+  }
+
+  mozilla::Telemetry::ID id;
+  nsresult rv = internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(), &id);
+  if (NS_SUCCEEDED(rv)) {
+    internal_RemoteAccumulate(id, aSample);
+  }
+}
+
+void
+internal_Accumulate(KeyedHistogram& aKeyed,
+                    const nsCString& aKey, uint32_t aSample)
+{
+  if (XRE_IsParentProcess()) {
+    aKeyed.Add(aKey, aSample);
+    return;
+  }
+
+  mozilla::Telemetry::ID id;
+  if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) {
+    internal_RemoteAccumulate(id, aKey, aSample);
+  }
+}
+
+void
+internal_AccumulateChild(mozilla::Telemetry::ID aId, uint32_t aSample)
+{
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  Histogram* h;
+  nsresult rv = internal_GetHistogramByEnumId(aId, &h, true);
+  if (NS_SUCCEEDED(rv)) {
+    internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
+  } else {
+    NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
+  }
+}
+
+void
+internal_AccumulateChildKeyed(mozilla::Telemetry::ID aId,
+                              const nsCString& aKey, uint32_t aSample)
+{
+  if (!gInitDone || !internal_CanRecordBase()) {
+    return;
+  }
+  const HistogramInfo& th = gHistograms[aId];
+  nsCString id;
+  id.Append(th.id());
+  id.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+  KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
+  MOZ_ASSERT(keyed);
+  keyed->Add(aKey, aSample);
+}
+
+} // namespace
+
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
 // PRIVATE: JSHistogram_* functions
 
 // NOTE: the functions in this section:
 //
 //   internal_JSHistogram_Add
 //   internal_JSHistogram_Snapshot
 //   internal_JSHistogram_Clear
 //   internal_JSHistogram_Dataset
@@ -1097,62 +1483,57 @@ internal_JSHistogram_Add(JSContext *cx, 
   Histogram::ClassType type = h->histogram_type();
 
   JS::CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!internal_CanRecordBase()) {
     return true;
   }
 
-  // If we don't have an argument for the count histogram, assume an increment of 1.
-  // Otherwise, make sure to run some sanity checks on the argument.
+  uint32_t value = 0;
+  mozilla::Telemetry::ID id;
   if ((type == base::CountHistogram::COUNT_HISTOGRAM) && (args.length() == 0)) {
-    internal_HistogramAdd(*h, 1);
-    return true;
-  }
-
-  // For categorical histograms we allow passing a string argument that specifies the label.
-  mozilla::Telemetry::ID id;
-  if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
+    // If we don't have an argument for the count histogram, assume an increment of 1.
+    // Otherwise, make sure to run some sanity checks on the argument.
+    value = 1;
+  } else if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
       (args.length() > 0) && args[0].isString() &&
       NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(), &id)) &&
       gHistograms[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) {
+    // For categorical histograms we allow passing a string argument that specifies the label.
     nsAutoJSString label;
     if (!label.init(cx, args[0])) {
       JS_ReportError(cx, "Invalid string parameter");
       return false;
     }
 
-    nsresult rv = internal_HistogramAddCategorical(id, NS_ConvertUTF16toUTF8(label));
+    nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
     if (NS_FAILED(rv)) {
       JS_ReportError(cx, "Unknown label for categorical histogram");
       return false;
     }
-
-    return true;
-  }
+  } else {
+    // All other accumulations expect one numerical argument.
+    if (!args.length()) {
+      JS_ReportError(cx, "Expected one argument");
+      return false;
+    }
 
-  // All other accumulations expect one numerical argument.
-  int32_t value = 0;
-  if (!args.length()) {
-    JS_ReportError(cx, "Expected one argument");
-    return false;
+    if (!(args[0].isNumber() || args[0].isBoolean())) {
+      JS_ReportError(cx, "Not a number");
+      return false;
+    }
+
+    if (!JS::ToUint32(cx, args[0], &value)) {
+      JS_ReportError(cx, "Failed to convert argument");
+      return false;
+    }
   }
 
-  if (!(args[0].isNumber() || args[0].isBoolean())) {
-    JS_ReportError(cx, "Not a number");
-    return false;
-  }
-
-  if (!JS::ToInt32(cx, args[0], &value)) {
-    JS_ReportError(cx, "Failed to convert argument");
-    return false;
-  }
-
-  internal_HistogramAdd(*h, value);
+  internal_Accumulate(*h, value);
   return true;
 }
 
 bool
 internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
@@ -1390,17 +1771,17 @@ internal_JSKeyedHistogram_Add(JSContext 
       return false;
     }
 
     if (!JS::ToInt32(cx, args[1], &value)) {
       return false;
     }
   }
 
-  keyed->Add(NS_ConvertUTF16toUTF8(key), value);
+  internal_Accumulate(*keyed, NS_ConvertUTF16toUTF8(key), value);
   return true;
 }
 
 bool
 internal_JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JSObject *obj = JS_THIS_OBJECT(cx, vp);
   if (!obj) {
@@ -1543,191 +1924,18 @@ internal_WrapAndReturnKeyedHistogram(Key
 }
 
 } // namespace
 
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 //
-// PRIVATE: functions related to addon histograms
-
-namespace {
-
-// Compute the name to pass into Histogram for the addon histogram
-// 'name' from the addon 'id'.  We can't use 'name' directly because it
-// might conflict with other histograms in other addons or even with our
-// own.
-void
-internal_AddonHistogramName(const nsACString &id, const nsACString &name,
-                            nsACString &ret)
-{
-  ret.Append(id);
-  ret.Append(':');
-  ret.Append(name);
-}
-
-bool
-internal_CreateHistogramForAddon(const nsACString &name,
-                                 AddonHistogramInfo &info)
-{
-  Histogram *h;
-  nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never",
-                                      info.histogramType, info.min, info.max,
-                                      info.bucketCount, true, &h);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-  // Don't let this histogram be reported via the normal means
-  // (e.g. Telemetry.registeredHistograms); we'll make it available in
-  // other ways.
-  h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
-  info.h = h;
-  return true;
-}
-
-bool
-internal_AddonHistogramReflector(AddonHistogramEntryType *entry,
-                                 JSContext *cx, JS::Handle<JSObject*> obj)
-{
-  AddonHistogramInfo &info = entry->mData;
-
-  // Never even accessed the histogram.
-  if (!info.h) {
-    // Have to force creation of HISTOGRAM_FLAG histograms.
-    if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
-      return true;
-
-    if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) {
-      return false;
-    }
-  }
-
-  if (internal_IsEmpty(info.h)) {
-    return true;
-  }
-
-  JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
-  if (!snapshot) {
-    // Just consider this to be skippable.
-    return true;
-  }
-  switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) {
-  case REFLECT_FAILURE:
-  case REFLECT_CORRUPT:
-    return false;
-  case REFLECT_OK:
-    const nsACString &histogramName = entry->GetKey();
-    if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(),
-                           snapshot, JSPROP_ENUMERATE)) {
-      return false;
-    }
-    break;
-  }
-  return true;
-}
-
-bool
-internal_AddonReflector(AddonEntryType *entry, JSContext *cx,
-                        JS::Handle<JSObject*> obj)
-{
-  const nsACString &addonId = entry->GetKey();
-  JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx));
-  if (!subobj) {
-    return false;
-  }
-
-  AddonHistogramMapType *map = entry->mData;
-  if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj)
-        && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(),
-                             subobj, JSPROP_ENUMERATE))) {
-    return false;
-  }
-  return true;
-}
-
-} // namespace
-
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
-// PRIVATE: thread-unsafe helpers for the external interface
-
-namespace {
-
-void
-internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled)
-{
-  if (!internal_IsHistogramEnumId(aID)) {
-    MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id");
-    return;
-  }
-
-  if (gHistograms[aID].keyed) {
-    const nsDependentCString id(gHistograms[aID].id());
-    KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
-    if (keyed) {
-      keyed->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  } else {
-    Histogram *h;
-    nsresult rv = internal_GetHistogramByEnumId(aID, &h);
-    if (NS_SUCCEEDED(rv)) {
-      h->SetRecordingEnabled(aEnabled);
-      return;
-    }
-  }
-
-  MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
-}
-
-void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample)
-{
-  if (!internal_CanRecordBase()) {
-    return;
-  }
-  Histogram *h;
-  nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
-  }
-}
-
-void
-internal_Accumulate(mozilla::Telemetry::ID aID,
-                    const nsCString& aKey, uint32_t aSample)
-{
-  if (!gInitDone || !internal_CanRecordBase()) {
-    return;
-  }
-  const HistogramInfo& th = gHistograms[aID];
-  KeyedHistogram* keyed
-     = internal_GetKeyedHistogramById(nsDependentCString(th.id()));
-  MOZ_ASSERT(keyed);
-  keyed->Add(aKey, aSample);
-}
-
-} // namespace
-
-
-////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////
-//
 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
 
-// This is a StaticMutex rather than a plain Mutex (1) so that
-// 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 gTelemetryHistogramMutex;
-
 // All of these functions are actually in namespace TelemetryHistogram::,
 // but the ::TelemetryHistogram prefix is given explicitly.  This is
 // because it is critical to see which calls from these functions are
 // to another function in this interface.  Mis-identifying "inwards
 // calls" from "calls to another function in this interface" will lead
 // to deadlocking and/or races.  See comments at the top of the file
 // for further (important!) details.
 
@@ -1781,16 +1989,26 @@ void TelemetryHistogram::InitializeGloba
     if (!h.keyed) {
       continue;
     }
 
     const nsDependentCString id(h.id());
     const nsDependentCString expiration(h.expiration());
     gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
                                                 h.min, h.max, h.bucketCount, h.dataset));
+    if (XRE_IsParentProcess()) {
+      // We must create registered child keyed histograms as well or else the
+      // same code in TelemetrySession.jsm that fails without parent keyed
+      // histograms will fail without child keyed histograms.
+      nsCString childId(id);
+      childId.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+      gKeyedHistograms.Put(childId,
+                           new KeyedHistogram(id, expiration, h.histogramType,
+                                              h.min, h.max, h.bucketCount, h.dataset));
+    }
   }
 
   // Some Telemetry histograms depend on the value of C++ constants and hardcode
   // their values in Histograms.json.
   // We add static asserts here for those values to match so that future changes
   // don't go unnoticed.
   // TODO: Compare explicitly with gHistograms[<histogram id>].bucketCount here
   // once we can make gHistograms constexpr (requires VS2015).
@@ -1811,16 +2029,21 @@ 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;
 }
@@ -1916,22 +2139,17 @@ TelemetryHistogram::Accumulate(const cha
   if (!internal_CanRecordBase()) {
     return;
   }
   mozilla::Telemetry::ID id;
   nsresult rv = internal_GetHistogramEnumId(name, &id);
   if (NS_FAILED(rv)) {
     return;
   }
-
-  Histogram *h;
-  rv = internal_GetHistogramByEnumId(id, &h);
-  if (NS_SUCCEEDED(rv)) {
-    internal_HistogramAdd(*h, sample, gHistograms[id].dataset);
-  }
+  internal_Accumulate(id, sample);
 }
 
 void
 TelemetryHistogram::Accumulate(const char* name,
                                const nsCString& key, uint32_t sample)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
@@ -1944,17 +2162,62 @@ TelemetryHistogram::Accumulate(const cha
   }
 }
 
 void
 TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::ID aId,
                                           const nsCString& label)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
-  internal_HistogramAddCategorical(aId, label);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  uint32_t labelId = 0;
+  if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) {
+    return;
+  }
+  internal_Accumulate(aId, labelId);
+}
+
+void
+TelemetryHistogram::AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
+    MOZ_ASSERT(isValid);
+    if (!isValid) {
+      continue;
+    }
+    internal_AccumulateChild(aAccumulations[i].mId, aAccumulations[i].mSample);
+  }
+}
+
+void
+TelemetryHistogram::AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  StaticMutexAutoLock locker(gTelemetryHistogramMutex);
+  if (!internal_CanRecordBase()) {
+    return;
+  }
+  for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
+    bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
+    MOZ_ASSERT(isValid);
+    if (!isValid) {
+      continue;
+    }
+    internal_AccumulateChildKeyed(aAccumulations[i].mId,
+                                  aAccumulations[i].mKey,
+                                  aAccumulations[i].mSample);
+  }
 }
 
 void
 TelemetryHistogram::ClearHistogram(mozilla::Telemetry::ID aId)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   if (!internal_CanRecordBase()) {
     return;
@@ -2053,16 +2316,18 @@ TelemetryHistogram::CreateHistogramSnaps
     }
     const uint32_t type = gHistograms[i].histogramType;
     if (type == nsITelemetry::HISTOGRAM_FLAG ||
         type == nsITelemetry::HISTOGRAM_COUNT) {
       Histogram *h;
       mozilla::DebugOnly<nsresult> rv
          = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
+      rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h, true);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   StatisticsRecorder::Histograms hs;
   StatisticsRecorder::GetHistograms(&hs);
 
   // We identify corrupt histograms first, rather than interspersing it
   // in the loop below, to ensure that our corruption statistics don't
@@ -2316,8 +2581,48 @@ TelemetryHistogram::GetHistogramSizesofI
   StatisticsRecorder::GetHistograms(&hs);
   size_t n = 0;
   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
     Histogram *h = *it;
     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);
+    }
+  }
+
+  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));
+    }
+  }
+
+  gIPCTimerArmed = false;
+}
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -3,16 +3,18 @@
  * 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 TelemetryHistogram_h__
 #define TelemetryHistogram_h__
 
 #include "mozilla/TelemetryHistogramEnums.h"
 
+#include "mozilla/TelemetryComms.h"
+
 // This module is internal to Telemetry.  It encapsulates Telemetry's
 // histogram accumulation and storage logic.  It should only be used by
 // Telemetry.cpp.  These functions should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace TelemetryHistogram {
 
 void CreateStatisticsRecorder();
@@ -37,16 +39,19 @@ nsresult SetHistogramRecordingEnabled(co
 void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
 void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey,
                                             uint32_t aSample);
 void Accumulate(const char* name, uint32_t sample);
 void Accumulate(const char* name, const nsCString& key, uint32_t sample);
 
 void AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& aLabel);
 
+void AccumulateChild(const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations);
+void AccumulateChildKeyed(const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations);
+
 void
 ClearHistogram(mozilla::Telemetry::ID aId);
 
 nsresult
 GetHistogramById(const nsACString &name, JSContext *cx,
                  JS::MutableHandle<JS::Value> ret);
 
 nsresult
@@ -91,11 +96,13 @@ 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__
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -36,33 +36,37 @@ const REASON_ABORTED_SESSION = "aborted-
 const REASON_DAILY = "daily";
 const REASON_SAVED_SESSION = "saved-session";
 const REASON_GATHER_PAYLOAD = "gather-payload";
 const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
 const REASON_TEST_PING = "test-ping";
 const REASON_ENVIRONMENT_CHANGE = "environment-change";
 const REASON_SHUTDOWN = "shutdown";
 
+const HISTOGRAM_SUFFIXES = {
+  PARENT: "",
+  CONTENT: "#content",
+}
+
 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::" : "::");
 
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID";
 const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit.enabled";
 const PREF_UNIFIED = PREF_BRANCH + "unified";
 
 
 const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload";
-const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload";
 const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs";
 const MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS = "Telemetry:GetChildThreadHangs";
 const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
 const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";
 
 const DATAREPORTING_DIRECTORY = "datareporting";
 const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
 
@@ -536,23 +540,16 @@ this.TelemetrySession = Object.freeze({
    * @param reason Optional, the reason to trigger the payload.
    * @param clearSubsession Optional, whether to clear subsession specific data.
    * @returns Object
    */
   getPayload: function(reason, clearSubsession = false) {
     return Impl.getPayload(reason, clearSubsession);
   },
   /**
-   * Asks the content processes to send their payloads.
-   * @returns Object
-   */
-  requestChildPayloads: function() {
-    return Impl.requestChildPayloads();
-  },
-  /**
    * Returns a promise that resolves to an array of thread hang stats from content processes, one entry per process.
    * The structure of each entry is identical to that of "threadHangStats" in nsITelemetry.
    * While thread hang stats are also part of the child payloads, this function is useful for cheaply getting this information,
    * which is useful for realtime hang monitoring.
    * Child processes that do not respond, or spawn/die during execution of this function are excluded from the result.
    * @returns Promise
    */
   getChildThreadHangs: function() {
@@ -879,33 +876,38 @@ var Impl = {
    * @return {Integer} A value from nsITelemetry.DATASET_*.
    */
   getDatasetType: function() {
     return Telemetry.canRecordExtended ? Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN
                                        : Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
   },
 
   getHistograms: function getHistograms(subsession, clearSubsession) {
-    this._log.trace("getHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
+    this._log.trace("getHistograms - subsession: " + subsession +
+                    ", clearSubsession: " + clearSubsession);
 
     let registered =
       Telemetry.registeredHistograms(this.getDatasetType(), []);
+    if (this._testing == false) {
+      // Omit telemetry test histograms outside of tests.
+      registered = registered.filter(n => !n.startsWith("TELEMETRY_TEST_"));
+    }
+    registered = registered.concat(registered.map(n => "STARTUP_" + n));
+
     let hls = subsession ? Telemetry.snapshotSubsessionHistograms(clearSubsession)
                          : Telemetry.histogramSnapshots;
     let ret = {};
 
     for (let name of registered) {
-      for (let n of [name, "STARTUP_" + name]) {
-        if (n in hls) {
-          // Omit telemetry test histograms outside of tests.
-          if (n.startsWith('TELEMETRY_TEST_') && this._testing == false) {
-            this._log.trace("getHistograms - Skipping test histogram: " + n);
-          } else {
-            ret[n] = this.packHistogram(hls[n]);
+      for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) {
+        if (name + suffix in hls) {
+          if (!(suffix in ret)) {
+            ret[suffix] = {};
           }
+          ret[suffix][name] = this.packHistogram(hls[name + suffix]);
         }
       }
     }
 
     return ret;
   },
 
   getAddonHistograms: function getAddonHistograms() {
@@ -923,46 +925,51 @@ var Impl = {
       if (Object.keys(packedHistograms).length != 0)
         ret[addonName] = packedHistograms;
     }
 
     return ret;
   },
 
   getKeyedHistograms: function(subsession, clearSubsession) {
-    this._log.trace("getKeyedHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
+    this._log.trace("getKeyedHistograms - subsession: " + subsession +
+                    ", clearSubsession: " + clearSubsession);
 
     let registered =
       Telemetry.registeredKeyedHistograms(this.getDatasetType(), []);
+    if (this._testing == false) {
+      // Omit telemetry test histograms outside of tests.
+      registered = registered.filter(id => !id.startsWith("TELEMETRY_TEST_"));
+    }
     let ret = {};
 
     for (let id of registered) {
-      // Omit telemetry test histograms outside of tests.
-      if (id.startsWith('TELEMETRY_TEST_') && this._testing == false) {
-        this._log.trace("getKeyedHistograms - Skipping test histogram: " + id);
-        continue;
-      }
-      let keyed = Telemetry.getKeyedHistogramById(id);
-      let snapshot = null;
-      if (subsession) {
-        snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear()
-                                   : keyed.subsessionSnapshot();
-      } else {
-        snapshot = keyed.snapshot();
-      }
+      for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) {
+        let keyed = Telemetry.getKeyedHistogramById(id + suffix);
+        let snapshot = null;
+        if (subsession) {
+          snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear()
+                                     : keyed.subsessionSnapshot();
+        } else {
+          snapshot = keyed.snapshot();
+        }
 
-      let keys = Object.keys(snapshot);
-      if (keys.length == 0) {
-        // Skip empty keyed histogram.
-        continue;
-      }
+        let keys = Object.keys(snapshot);
+        if (keys.length == 0) {
+          // Skip empty keyed histogram.
+          continue;
+        }
 
-      ret[id] = {};
-      for (let key of keys) {
-        ret[id][key] = this.packHistogram(snapshot[key]);
+        if (!(suffix in ret)) {
+          ret[suffix] = {};
+        }
+        ret[suffix][id] = {};
+        for (let key of keys) {
+          ret[suffix][id][key] = this.packHistogram(snapshot[key]);
+        }
       }
     }
 
     return ret;
   },
 
   getScalars: function (subsession, clearSubsession) {
     this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " + clearSubsession);
@@ -1238,53 +1245,58 @@ var Impl = {
   assemblePayloadWithMeasurements: function(simpleMeasurements, info, reason, clearSubsession) {
     const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
     clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
     this._log.trace("assemblePayloadWithMeasurements - reason: " + reason +
                     ", submitting subsession data: " + isSubsession);
 
     // This allows wrapping data retrieval calls in a try-catch block so that
     // failures don't break the rest of the ping assembly.
-    const protect = (fn) => {
+    const protect = (fn, defaultReturn = null) => {
       try {
         return fn();
       } catch (ex) {
         this._log.error("assemblePayloadWithMeasurements - caught exception", ex);
-        return null;
+        return defaultReturn;
       }
     };
 
     // Payload common to chrome and content processes.
     let payloadObj = {
       ver: PAYLOAD_VERSION,
       simpleMeasurements: simpleMeasurements,
-      histograms: protect(() => this.getHistograms(isSubsession, clearSubsession)),
-      keyedHistograms: protect(() => this.getKeyedHistograms(isSubsession, clearSubsession)),
     };
 
     // Add extended set measurements common to chrome & content processes
     if (Telemetry.canRecordExtended) {
       payloadObj.chromeHangs = protect(() => Telemetry.chromeHangs);
       payloadObj.threadHangStats = protect(() => this.getThreadHangStats(Telemetry.threadHangStats));
       payloadObj.log = protect(() => TelemetryLog.entries());
       payloadObj.webrtc = protect(() => Telemetry.webrtcStats);
     }
 
     if (Utils.isContentProcess) {
       return payloadObj;
     }
 
-    // Set the scalars for the parent process.
+    // Additional payload for chrome process.
+    let histograms = protect(() => this.getHistograms(isSubsession, clearSubsession), {});
+    let keyedHistograms = protect(() => this.getKeyedHistograms(isSubsession, clearSubsession), {});
+    payloadObj.histograms = histograms[HISTOGRAM_SUFFIXES.PARENT] || {};
+    payloadObj.keyedHistograms = keyedHistograms[HISTOGRAM_SUFFIXES.PARENT] || {};
     payloadObj.processes = {
       parent: {
         scalars: protect(() => this.getScalars(isSubsession, clearSubsession)),
-      }
+      },
+      content: {
+        histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT],
+        keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.CONTENT],
+      },
     };
 
-    // Additional payload for chrome process.
     payloadObj.info = info;
 
     // Add extended set measurements for chrome process.
     if (Telemetry.canRecordExtended) {
       payloadObj.slowSQL = protect(() => Telemetry.slowSQL);
       payloadObj.fileIOReports = protect(() => Telemetry.fileIOReports);
       payloadObj.lateWrites = protect(() => Telemetry.lateWrites);
 
@@ -1516,17 +1528,16 @@ var Impl = {
     this._testing = testing;
 
     if (!Telemetry.canRecordBase) {
       this._log.trace("setupContentProcess - base recording is disabled, not initializing");
       return;
     }
 
     Services.obs.addObserver(this, "content-child-shutdown", false);
-    cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, this);
     cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS, this);
     cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this);
 
     this.gatherStartupHistograms();
 
     let delayedTask = new DeferredTask(function* () {
       this._initialized = true;
 
@@ -1554,42 +1565,28 @@ var Impl = {
     this._log.trace("receiveMessage - Message name " + message.name);
     switch (message.name) {
     case MESSAGE_TELEMETRY_PAYLOAD:
     {
       // In parent process, receive Telemetry payload from child
       let source = message.data.childUUID;
       delete message.data.childUUID;
 
-      for (let child of this._childTelemetry) {
-        if (child.source === source) {
-          // Update existing telemetry data.
-          child.payload = message.data;
-          return;
-        }
-      }
-      // Did not find existing child in this._childTelemetry.
       this._childTelemetry.push({
         source: source,
         payload: message.data,
       });
 
       if (this._childTelemetry.length == MAX_NUM_CONTENT_PAYLOADS + 1) {
         this._childTelemetry.shift();
         Telemetry.getHistogramById("TELEMETRY_DISCARDED_CONTENT_PINGS_COUNT").add();
       }
 
       break;
     }
-    case MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD:
-    {
-      // In child process, send the requested Telemetry payload
-      this.sendContentProcessPing("saved-session");
-      break;
-    }
     case MESSAGE_TELEMETRY_THREAD_HANGS:
     {
       // Accumulate child thread hang stats from this child
       this._childThreadHangs.push(message.data);
 
       // Check if we've got data from all the children, accounting for child processes dying
       // if it happens before the last response is received and no new child processes are spawned at the exact same time
       // If that happens, we can resolve the promise earlier rather than having to wait for the timeout to expire
@@ -1759,21 +1756,16 @@ var Impl = {
     if (Object.keys(this._slowSQLStartup).length == 0) {
       this.gatherStartupHistograms();
       this._slowSQLStartup = Telemetry.slowSQL;
     }
     this.gatherMemory();
     return this.getSessionPayload(reason, clearSubsession);
   },
 
-  requestChildPayloads: function() {
-    this._log.trace("requestChildPayloads");
-    ppmm.broadcastAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, {});
-  },
-
   getChildThreadHangs: function getChildThreadHangs() {
     return new Promise((resolve) => {
       // Return immediately if there are no child processes to get stats from
       if (ppmm.childCount === 0) {
         resolve([]);
         return;
       }
 
@@ -1841,17 +1833,17 @@ var Impl = {
       this._log.trace("observe - " + aTopic + " notified.");
     }
 
     switch (aTopic) {
     case "content-child-shutdown":
       // content-child-shutdown is only registered for content processes.
       Services.obs.removeObserver(this, "content-child-shutdown");
       this.uninstall();
-
+      Telemetry.flushBatchedChildTelemetry();
       this.sendContentProcessPing(REASON_SAVED_SESSION);
       break;
     case TOPIC_CYCLE_COLLECTOR_BEGIN:
       let now = new Date();
       if (!gLastMemoryPoll
           || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) {
         gLastMemoryPoll = now;
         this.gatherMemory();
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -14,16 +14,17 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'telemetry'
 
 EXPORTS.mozilla += [
     '!TelemetryHistogramEnums.h',
     '!TelemetryScalarEnums.h',
     'ProcessedStack.h',
     'Telemetry.h',
+    'TelemetryComms.h',
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
     'TelemetryCommon.cpp',
     'TelemetryHistogram.cpp',
     'TelemetryScalar.cpp',
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -391,9 +391,15 @@ interface nsITelemetry : nsISupports
    */
   [implicit_jscontext, optional_argc]
   jsval snapshotScalars(in uint32_t aDataset, [optional] in boolean aClear);
 
   /**
    * Resets all the stored scalars. This is intended to be only used in tests.
    */
   void clearScalars();
+
+  /**
+   * Immediately sends any Telemetry batched on this process to the parent
+   * process. This is intended only to be used on process shutdown.
+   */
+  void flushBatchedChildTelemetry();
 };
--- a/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js
@@ -1,12 +1,13 @@
 
 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_TELEMETRY_PAYLOAD = "Telemetry:Payload";
 const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload";
 const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done";
 
 const PLATFORM_VERSION = "1.9.2";
 const APP_VERSION = "1";
 const APP_ID = "xpcshell@tests.mozilla.org";
@@ -14,38 +15,40 @@ const APP_NAME = "XPCShell";
 
 function run_child_test() {
   // Setup histograms with some fixed values.
   let flagHist = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG");
   flagHist.add(1);
   let countHist = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT");
   countHist.add();
   countHist.add();
+  let categHist = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL");
+  categHist.add("Label2");
+  categHist.add("Label3");
 
   let flagKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_FLAG");
   flagKeyed.add("a", 1);
   flagKeyed.add("b", 1);
   let countKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT");
   countKeyed.add("a");
   countKeyed.add("b");
   countKeyed.add("b");
-
-  // Check payload values.
-  const payload = TelemetrySession.getPayload("test-ping");
-  check_histogram_values(payload);
 }
 
 function check_histogram_values(payload) {
   const hs = payload.histograms;
   Assert.ok("TELEMETRY_TEST_COUNT" in hs, "Should have count test histogram.");
   Assert.ok("TELEMETRY_TEST_FLAG" in hs, "Should have flag test histogram.");
+  Assert.ok("TELEMETRY_TEST_CATEGORICAL" in hs, "Should have categorical test histogram.");
   Assert.equal(hs["TELEMETRY_TEST_COUNT"].sum, 2,
                "Count test histogram should have the right value.");
   Assert.equal(hs["TELEMETRY_TEST_FLAG"].sum, 1,
                "Flag test histogram should have the right value.");
+  Assert.equal(hs["TELEMETRY_TEST_CATEGORICAL"].sum, 3,
+               "Categorical test histogram should have the right sum.");
 
   const kh = payload.keyedHistograms;
   Assert.ok("TELEMETRY_TEST_KEYED_COUNT" in kh, "Should have keyed count test histogram.");
   Assert.ok("TELEMETRY_TEST_KEYED_FLAG" in kh, "Should have keyed flag test histogram.");
   Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["a"].sum, 1,
                "Keyed count test histogram should have the right value.");
   Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["b"].sum, 2,
                "Keyed count test histogram should have the right value.");
@@ -56,18 +59,16 @@ function check_histogram_values(payload)
 }
 
 add_task(function*() {
   if (!runningInParent) {
     TelemetryController.testSetupContent();
     run_child_test();
     dump("... done with child test\n");
     do_send_remote_message(MESSAGE_CHILD_TEST_DONE);
-    dump("... waiting for child payload collection\n");
-    yield do_await_remote_message(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD);
     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();
@@ -75,25 +76,25 @@ add_task(function*() {
     // 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.
   let childPromise = run_test_in_child("test_ChildHistograms.js");
   yield do_await_remote_message(MESSAGE_CHILD_TEST_DONE);
 
-  // Gather payload from child.
-  dump("... requesting child payloads\n");
-  let promiseMessage = do_await_remote_message(MESSAGE_TELEMETRY_PAYLOAD);
-  TelemetrySession.requestChildPayloads();
-  yield promiseMessage;
-  dump("... received child payload\n");
-
-  // Check child payload.
+  yield ContentTaskUtils.waitForCondition(() => {
+    let payload = TelemetrySession.getPayload("test-ping");
+    return payload &&
+           "processes" in payload &&
+           "content" in payload.processes &&
+           "histograms" in payload.processes.content &&
+           "TELEMETRY_TEST_COUNT" in payload.processes.content.histograms;
+  });
   const payload = TelemetrySession.getPayload("test-ping");
-  Assert.ok("childPayloads" in payload, "Should have child payloads.");
-  Assert.equal(payload.childPayloads.length, 1, "Should have received one child payload so far.");
-  Assert.ok("histograms" in payload.childPayloads[0], "Child payload should have histograms.");
-  Assert.ok("keyedHistograms" in payload.childPayloads[0], "Child payload should have keyed histograms.");
-  check_histogram_values(payload.childPayloads[0]);
+  Assert.ok("processes" in payload, "Should have processes section");
+  Assert.ok("content" in payload.processes, "Should have child process section");
+  Assert.ok("histograms" in payload.processes.content, "Child process section should have histograms.");
+  Assert.ok("keyedHistograms" in payload.processes.content, "Child process section should have keyed histograms.");
+  check_histogram_values(payload.processes.content);
 
   do_test_finished();
 });
--- a/toolkit/content/aboutTelemetry.css
+++ b/toolkit/content/aboutTelemetry.css
@@ -224,16 +224,24 @@ body[dir="rtl"] .copy-node {
   padding-inline-start: 10em;
   display: none;
 }
 
 .has-data.expanded .filter-ui {
   display: inline;
 }
 
+.processes-ui {
+  display: none;
+}
+
+.has-data.expanded .processes-ui {
+  display: initial;
+}
+
 .filter-blocked {
   display: none;
 }
 
 #raw-ping-data-section {
   width: 100%;
   height: 100%;
   background-color:-moz-Dialog;
@@ -252,8 +260,12 @@ body[dir="rtl"] .copy-node {
   padding: 5px 10px;
 }
 
 /* addon subsection style */
 .addon-caption {
   font-size: larger;
   margin: 5px 0;
 }
+
+.process-picker {
+  margin: 0 0.5em;
+}
--- 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("histograms-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
+    document.getElementById("keyed-histograms-processes")
+            .addEventListener("change", () => displayPingData(gPingData), false);
   },
 
   onPingSourceChanged: function() {
     this.update();
   },
 
   onPingDisplayChanged: function() {
     this.update();
@@ -1777,16 +1781,43 @@ function sortStartupMilestones(aSimpleMe
   let result = {};
   for (let key of sortedKeys) {
     result[key] = aSimpleMeasurements[key];
   }
 
   return result;
 }
 
+function renderProcessList(ping, selectEl) {
+  removeAllChildNodes(selectEl);
+  let option = document.createElement("option");
+  option.appendChild(document.createTextNode("parent"));
+  option.setAttribute("value", "");
+  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:
+    if (process === "parent") {
+      continue;
+    }
+    option = document.createElement("option");
+    option.appendChild(document.createTextNode(process));
+    option.setAttribute("value", process);
+    selectEl.appendChild(option);
+  }
+}
+
 function renderPayloadList(ping) {
   // Rebuild the payload select with options:
   //   Parent Payload (selected)
   //   Child Payload 1..ping.payload.childPayloads.length
   let listEl = document.getElementById("choose-payload");
   removeAllChildNodes(listEl);
 
   let option = document.createElement("option");
@@ -1845,19 +1876,21 @@ function displayPingData(ping, updatePay
   // Render raw ping data.
   let pre = document.getElementById("raw-ping-data");
   pre.textContent = JSON.stringify(gPingData, null, 2);
 
   // Update the structured data rendering.
   const keysHeader = bundle.GetStringFromName("keysHeader");
   const valuesHeader = bundle.GetStringFromName("valuesHeader");
 
-  // Update the payload list
+  // Update the payload list and process lists
   if (updatePayloadList) {
     renderPayloadList(ping);
+    renderProcessList(ping, document.getElementById("histograms-processes"));
+    renderProcessList(ping, document.getElementById("keyed-histograms-processes"));
   }
 
   // Show general data.
   GeneralData.render(ping);
 
   // Show environment data.
   EnvironmentData.render(ping);
 
@@ -1924,18 +1957,28 @@ function displayPingData(ping, updatePay
   // Show scalar data.
   Scalars.render(payload);
 
   // Show histogram data
   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");
+  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);
+  setHasData("histograms-section", hasData || hgramsSelect.options.length);
 
   if (hasData) {
     for (let [name, hgram] of Object.entries(histograms)) {
       Histogram.render(hgramDiv, name, hgram, {unpacked: true});
     }
 
     let filterBox = document.getElementById("histograms-filter");
     filterBox.addEventListener("input", Histogram.histogramFilterChanged, false);
@@ -1945,27 +1988,37 @@ function displayPingData(ping, updatePay
 
     setHasData("histograms-section", true);
   }
 
   // Show keyed histogram data
   let keyedDiv = document.getElementById("keyed-histograms");
   removeAllChildNodes(keyedDiv);
 
-  setHasData("keyed-histograms-section", false);
   let keyedHistograms = payload.keyedHistograms;
+
+  let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
+  let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
+  let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
+  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) {
     let hasData = false;
     for (let [id, keyed] of Object.entries(keyedHistograms)) {
       if (Object.keys(keyed).length > 0) {
         hasData = true;
         KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
       }
     }
-    setHasData("keyed-histograms-section", hasData);
+    setHasData("keyed-histograms-section", hasData || keyedHgramsSelect.options.length);
   }
 
   // Show addon histogram data
   let addonDiv = document.getElementById("addon-histograms");
   removeAllChildNodes(addonDiv);
 
   let addonHistogramsRendered = false;
   let addonData = payload.addonHistograms;
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -147,25 +147,31 @@
       <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>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
         <span class="filter-ui">
           &aboutTelemetry.filterText; <input type="text" class="filter" id="histograms-filter" target_id="histograms"/>
         </span>
+        <div class="processes-ui">
+          <select id="histograms-processes" class="process-picker"></select>
+        </div>
         <div id="histograms" class="data">
         </div>
       </section>
 
       <section id="keyed-histograms-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.keyedHistogramsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
         <span class="empty-caption">&aboutTelemetry.emptySection;</span>
+        <div class="processes-ui">
+          <select id="keyed-histograms-processes" class="process-picker"></select>
+        </div>
         <div id="keyed-histograms" class="data">
         </div>
       </section>
 
       <section id="simple-measurements-section" class="data-section">
         <input type="checkbox" class="statebox"/>
         <h1 class="section-name">&aboutTelemetry.simpleMeasurementsSection;</h1>
         <span class="toggle-caption">&aboutTelemetry.toggle;</span>
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -1,13 +1,14 @@
 /* -*- 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/. */
 
+#include "gfxPrefs.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
@@ -407,45 +408,25 @@ WidgetInputEvent::AccelModifier()
   }
   return sAccelModifier;
 }
 
 /******************************************************************************
  * mozilla::WidgetWheelEvent (MouseEvents.h)
  ******************************************************************************/
 
-bool WidgetWheelEvent::sInitialized = false;
-bool WidgetWheelEvent::sIsSystemScrollSpeedOverrideEnabled = false;
-int32_t WidgetWheelEvent::sOverrideFactorX = 0;
-int32_t WidgetWheelEvent::sOverrideFactorY = 0;
-
-/* static */ void
-WidgetWheelEvent::Initialize()
-{
-  if (sInitialized) {
-    return;
-  }
-
-  Preferences::AddBoolVarCache(&sIsSystemScrollSpeedOverrideEnabled,
-    "mousewheel.system_scroll_override_on_root_content.enabled", false);
-  Preferences::AddIntVarCache(&sOverrideFactorX,
-    "mousewheel.system_scroll_override_on_root_content.horizontal.factor", 0);
-  Preferences::AddIntVarCache(&sOverrideFactorY,
-    "mousewheel.system_scroll_override_on_root_content.vertical.factor", 0);
-  sInitialized = true;
-}
-
 /* static */ double
 WidgetWheelEvent::ComputeOverriddenDelta(double aDelta, bool aIsForVertical)
 {
-  Initialize();
-  if (!sIsSystemScrollSpeedOverrideEnabled) {
+  if (!gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) {
     return aDelta;
   }
-  int32_t intFactor = aIsForVertical ? sOverrideFactorY : sOverrideFactorX;
+  int32_t intFactor = aIsForVertical
+                      ? gfxPrefs::MouseWheelRootScrollVerticalFactor()
+                      : gfxPrefs::MouseWheelRootScrollHorizontalFactor();
   // Making the scroll speed slower doesn't make sense. So, ignore odd factor
   // which is less than 1.0.
   if (intFactor <= 100) {
     return aDelta;
   }
   double factor = static_cast<double>(intFactor) / 100;
   return aDelta * factor;
 }